Browse Source

SONAR-6730 Simplify API

tags/5.2-RC1
Julien Lancelot 8 years ago
parent
commit
b707f2c2b6
24 changed files with 614 additions and 746 deletions
  1. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/ComponentIssuesRepository.java
  2. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolder.java
  3. 4
    4
      server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolderImpl.java
  4. 8
    6
      server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersVisitor.java
  5. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/computation/measure/MutableMeasureComputersHolder.java
  6. 23
    14
      server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerContextImpl.java
  7. 14
    32
      server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerDefinitionImpl.java
  8. 0
    89
      server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContext.java
  9. 52
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerWrapper.java
  10. 114
    52
      server/sonar-server/src/main/java/org/sonar/server/computation/step/FeedMeasureComputers.java
  11. 4
    4
      server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureComputersHolderImplTest.java
  12. 27
    19
      server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureComputersVisitorTest.java
  13. 33
    43
      server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerContextImplTest.java
  14. 29
    78
      server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerDefinitionImplTest.java
  15. 0
    153
      server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContextTest.java
  16. 122
    94
      server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedMeasureComputersTest.java
  17. 1
    2
      sonar-plugin-api/src/main/java/org/sonar/api/batch/Decorator.java
  18. 5
    0
      sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/Component.java
  19. 5
    0
      sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/Issue.java
  20. 5
    0
      sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/Measure.java
  21. 159
    97
      sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/MeasureComputer.java
  22. 0
    53
      sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/MeasureComputerProvider.java
  23. 1
    0
      sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/RangeDistributionBuilder.java
  24. 3
    1
      sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/Settings.java

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/issue/ComponentIssuesRepository.java View File

@@ -27,7 +27,7 @@ import org.sonar.server.computation.component.Component;

/**
* This repository contains issues for only one component at a time. It's populated by {@link IntegrateIssuesVisitor} and
* it's mainly used by {@link org.sonar.server.computation.measure.api.MeasureComputerImplementationContext} in order for a {@link MeasureComputer}
* it's mainly used by {@link org.sonar.api.ce.measure.MeasureComputer.MeasureComputerContext} in order for a {@link MeasureComputer}
* to access to the issues of a component.
*
* This repository must NEVER contains more issues than in issues from one component order to not consume to much memory.

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolder.java View File

@@ -20,7 +20,7 @@

package org.sonar.server.computation.measure;

import org.sonar.api.ce.measure.MeasureComputer;
import org.sonar.server.computation.measure.api.MeasureComputerWrapper;

public interface MeasureComputersHolder {

@@ -29,5 +29,5 @@ public interface MeasureComputersHolder {
*
* @throws IllegalStateException if the measure computers haven't been initialized
*/
Iterable<MeasureComputer> getMeasureComputers();
Iterable<MeasureComputerWrapper> getMeasureComputers();
}

+ 4
- 4
server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolderImpl.java View File

@@ -22,7 +22,7 @@ package org.sonar.server.computation.measure;

import com.google.common.base.Predicates;
import javax.annotation.CheckForNull;
import org.sonar.api.ce.measure.MeasureComputer;
import org.sonar.server.computation.measure.api.MeasureComputerWrapper;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.FluentIterable.from;
@@ -31,16 +31,16 @@ import static java.util.Objects.requireNonNull;
public class MeasureComputersHolderImpl implements MutableMeasureComputersHolder {

@CheckForNull
private Iterable<MeasureComputer> measureComputers;
private Iterable<MeasureComputerWrapper> measureComputers;

@Override
public Iterable<MeasureComputer> getMeasureComputers() {
public Iterable<MeasureComputerWrapper> getMeasureComputers() {
checkState(this.measureComputers != null, "Measure computers have not been initialized yet");
return measureComputers;
}

@Override
public void setMeasureComputers(Iterable<MeasureComputer> measureComputers) {
public void setMeasureComputers(Iterable<MeasureComputerWrapper> measureComputers) {
requireNonNull(measureComputers, "Measure computers cannot be null");
checkState(this.measureComputers == null, "Measure computers have already been initialized");
this.measureComputers = from(measureComputers).filter(Predicates.notNull()).toList();

+ 8
- 6
server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersVisitor.java View File

@@ -27,7 +27,8 @@ import org.sonar.server.computation.component.CrawlerDepthLimit;
import org.sonar.server.computation.component.SettingsRepository;
import org.sonar.server.computation.component.TypeAwareVisitorAdapter;
import org.sonar.server.computation.issue.ComponentIssuesRepository;
import org.sonar.server.computation.measure.api.MeasureComputerImplementationContext;
import org.sonar.server.computation.measure.api.MeasureComputerContextImpl;
import org.sonar.server.computation.measure.api.MeasureComputerWrapper;
import org.sonar.server.computation.metric.MetricRepository;

import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER;
@@ -55,11 +56,12 @@ public class MeasureComputersVisitor extends TypeAwareVisitorAdapter {

@Override
public void visitAny(org.sonar.server.computation.component.Component component) {
for (MeasureComputer computer : measureComputersHolder.getMeasureComputers()) {
LOGGER.trace("Measure computer '{}' is computing component {}", computer.getImplementation(), component);
MeasureComputerImplementationContext measureComputerContext = new MeasureComputerImplementationContext(component, computer,
settings, measureRepository, metricRepository, componentIssuesRepository);
computer.getImplementation().compute(measureComputerContext);
MeasureComputerContextImpl context = new MeasureComputerContextImpl(component, settings, measureRepository, metricRepository, componentIssuesRepository);
for (MeasureComputerWrapper measureComputerWrapper : measureComputersHolder.getMeasureComputers()) {
context.setDefinition(measureComputerWrapper.getDefinition());
MeasureComputer measureComputer = measureComputerWrapper.getComputer();
LOGGER.trace("Measure computer '{}' is computing component {}", measureComputer, component);
measureComputer.compute(context);
}
}
}

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/measure/MutableMeasureComputersHolder.java View File

@@ -19,7 +19,7 @@
*/
package org.sonar.server.computation.measure;

import org.sonar.api.ce.measure.MeasureComputer;
import org.sonar.server.computation.measure.api.MeasureComputerWrapper;

/**
* A {@link MeasureComputersHolder} which value can be set only once.
@@ -32,5 +32,5 @@ public interface MutableMeasureComputersHolder extends MeasureComputersHolder {
* @throws NullPointerException if the specified Iterable is {@code null}
* @throws IllegalStateException if the holder has already been initialized
*/
void setMeasureComputers(Iterable<MeasureComputer> measureComputers);
void setMeasureComputers(Iterable<MeasureComputerWrapper> measureComputers);
}

server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContext.java → server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerContextImpl.java View File

@@ -33,7 +33,6 @@ import javax.annotation.Nullable;
import org.sonar.api.ce.measure.Component;
import org.sonar.api.ce.measure.Issue;
import org.sonar.api.ce.measure.Measure;
import org.sonar.api.ce.measure.MeasureComputer;
import org.sonar.api.ce.measure.Settings;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.server.computation.component.SettingsRepository;
@@ -43,11 +42,12 @@ import org.sonar.server.computation.metric.Metric;
import org.sonar.server.computation.metric.MetricRepository;

import static com.google.common.base.Preconditions.checkArgument;
import static org.sonar.api.ce.measure.MeasureComputer.MeasureComputerContext;
import static org.sonar.api.ce.measure.MeasureComputer.MeasureComputerDefinition;
import static org.sonar.server.computation.measure.Measure.newMeasureBuilder;

public class MeasureComputerImplementationContext implements MeasureComputer.Implementation.Context {
public class MeasureComputerContextImpl implements MeasureComputerContext {

private final MeasureComputer measureComputer;
private final SettingsRepository settings;
private final MeasureRepository measureRepository;
private final MetricRepository metricRepository;
@@ -56,18 +56,17 @@ public class MeasureComputerImplementationContext implements MeasureComputer.Imp
private final Component component;
private final List<DefaultIssue> componentIssues;

private final Set<String> allowedMetrics;
private MeasureComputerDefinition definition;
private Set<String> allowedMetrics;

public MeasureComputerImplementationContext(org.sonar.server.computation.component.Component component, MeasureComputer measureComputer,
SettingsRepository settings, MeasureRepository measureRepository, MetricRepository metricRepository, ComponentIssuesRepository componentIssuesRepository) {
this.measureComputer = measureComputer;
public MeasureComputerContextImpl(org.sonar.server.computation.component.Component component, SettingsRepository settings,
MeasureRepository measureRepository, MetricRepository metricRepository, ComponentIssuesRepository componentIssuesRepository) {
this.settings = settings;
this.internalComponent = component;
this.measureRepository = measureRepository;
this.metricRepository = metricRepository;
this.component = newComponent(component);
this.componentIssues = componentIssuesRepository.getIssues(component);
this.allowedMetrics = allowedMetric(measureComputer);
}

private Component newComponent(org.sonar.server.computation.component.Component component) {
@@ -79,10 +78,20 @@ public class MeasureComputerImplementationContext implements MeasureComputer.Imp
null);
}

private static Set<String> allowedMetric(MeasureComputer measureComputer) {
/**
* Definition needs to be reset each time a new computer is processed.
* Defining it by a setter allows to reduce the number of this class to be created (one per component instead of one per component and per computer).
*/
public MeasureComputerContextImpl setDefinition(MeasureComputerDefinition definition) {
this.definition = definition;
this.allowedMetrics = allowedMetric(definition);
return this;
}

private static Set<String> allowedMetric(MeasureComputerDefinition definition) {
Set<String> allowedMetrics = new HashSet<>();
allowedMetrics.addAll(measureComputer.getInputMetrics());
allowedMetrics.addAll(measureComputer.getOutputMetrics());
allowedMetrics.addAll(definition.getInputMetrics());
allowedMetrics.addAll(definition.getOutputMetrics());
return allowedMetrics;
}

@@ -160,12 +169,12 @@ public class MeasureComputerImplementationContext implements MeasureComputer.Imp
}

private void validateInputMetric(String metric) {
checkArgument(allowedMetrics.contains(metric), "Only metrics in %s can be used to load measures", measureComputer.getInputMetrics());
checkArgument(allowedMetrics.contains(metric), "Only metrics in %s can be used to load measures", definition.getInputMetrics());
}

private void validateAddMeasure(Metric metric) {
checkArgument(measureComputer.getOutputMetrics().contains(metric.getKey()), "Only metrics in %s can be used to add measures. Metric '%s' is not allowed.",
measureComputer.getOutputMetrics(), metric.getKey());
checkArgument(definition.getOutputMetrics().contains(metric.getKey()), "Only metrics in %s can be used to add measures. Metric '%s' is not allowed.",
definition.getOutputMetrics(), metric.getKey());
if (measureRepository.getRawMeasure(internalComponent, metric).isPresent()) {
throw new UnsupportedOperationException(String.format("A measure on metric '%s' already exists on component '%s'", metric.getKey(), internalComponent.getKey()));
}

server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImpl.java → server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerDefinitionImpl.java View File

@@ -22,22 +22,21 @@ package org.sonar.server.computation.measure.api;

import com.google.common.collect.ImmutableSet;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.ce.measure.MeasureComputer;

import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;

public class MeasureComputerImpl implements MeasureComputer {
public class MeasureComputerDefinitionImpl implements MeasureComputer.MeasureComputerDefinition {

private final Set<String> inputMetricKeys;
private final Set<String> outputMetrics;
private final Implementation implementation;

public MeasureComputerImpl(MeasureComputerBuilderImpl builder) {
private MeasureComputerDefinitionImpl(BuilderImpl builder) {
this.inputMetricKeys = ImmutableSet.copyOf(builder.inputMetricKeys);
this.outputMetrics = ImmutableSet.copyOf(builder.outputMetrics);
this.implementation = builder.measureComputerImplementation;
}

@Override
@@ -50,11 +49,6 @@ public class MeasureComputerImpl implements MeasureComputer {
return outputMetrics;
}

@Override
public Implementation getImplementation() {
return implementation;
}

@Override
public boolean equals(Object o) {
if (this == o) {
@@ -64,7 +58,7 @@ public class MeasureComputerImpl implements MeasureComputer {
return false;
}

MeasureComputerImpl that = (MeasureComputerImpl) o;
MeasureComputerDefinitionImpl that = (MeasureComputerDefinitionImpl) o;

if (!inputMetricKeys.equals(that.inputMetricKeys)) {
return false;
@@ -81,43 +75,35 @@ public class MeasureComputerImpl implements MeasureComputer {

@Override
public String toString() {
return "MeasureComputerImpl{" +
return "MeasureComputerDefinitionImpl{" +
"inputMetricKeys=" + inputMetricKeys +
", outputMetrics=" + outputMetrics +
", implementation=" + implementation +
'}';
}

public static class MeasureComputerBuilderImpl implements MeasureComputerBuilder {
public static class BuilderImpl implements Builder {

private String[] inputMetricKeys = new String[] {};
@CheckForNull
private String[] outputMetrics;
private Implementation measureComputerImplementation;

@Override
public MeasureComputerBuilder setInputMetrics(String... inputMetrics) {
public Builder setInputMetrics(String... inputMetrics) {
this.inputMetricKeys = validateInputMetricKeys(inputMetrics);
return this;
}

@Override
public MeasureComputerBuilder setOutputMetrics(String... outputMetrics) {
public Builder setOutputMetrics(String... outputMetrics) {
this.outputMetrics = validateOutputMetricKeys(outputMetrics);
return this;
}

@Override
public MeasureComputerBuilder setImplementation(Implementation impl) {
this.measureComputerImplementation = validateImplementation(impl);
return this;
}

@Override
public MeasureComputer build() {
public MeasureComputer.MeasureComputerDefinition build() {
validateInputMetricKeys(this.inputMetricKeys);
validateOutputMetricKeys(this.outputMetrics);
validateImplementation(this.measureComputerImplementation);
return new MeasureComputerImpl(this);
return new MeasureComputerDefinitionImpl(this);
}

private static String[] validateInputMetricKeys(@Nullable String[] inputMetrics) {
@@ -127,20 +113,16 @@ public class MeasureComputerImpl implements MeasureComputer {
}

private static String[] validateOutputMetricKeys(@Nullable String[] outputMetrics) {
checkArgument(outputMetrics != null && outputMetrics.length > 0, "At least one output metric must be defined");
requireNonNull(outputMetrics, "Output metrics cannot be null");
checkArgument(outputMetrics.length > 0, "At least one output metric must be defined");
checkNotNull(outputMetrics);
return outputMetrics;
}

private static Implementation validateImplementation(Implementation impl) {
return requireNonNull(impl, "The implementation is missing");
}
}

private static void checkNotNull(String[] metrics){
private static void checkNotNull(String[] metrics) {
for (String metric : metrics) {
requireNonNull(metric, "Null metric is not allowed");
}
}

}

+ 0
- 89
server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContext.java View File

@@ -1,89 +0,0 @@
/*
* 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.measure.api;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.sonar.api.ce.measure.MeasureComputer;
import org.sonar.api.ce.measure.MeasureComputerProvider;

public class MeasureComputerProviderContext implements MeasureComputerProvider.Context {

private final List<MeasureComputer> measureComputers = new ArrayList<>();
private final Map<String, MeasureComputer> computerByOutputMetrics = new HashMap<>();

@Override
public MeasureComputerProvider.Context add(MeasureComputer inputMeasureComputer) {
MeasureComputer measureComputer = validateMeasureComputer(inputMeasureComputer);
checkOutputMetricsNotAlreadyDefinedByAnotherComputer(measureComputer);
this.measureComputers.add(measureComputer);
for (String metric : measureComputer.getOutputMetrics()) {
computerByOutputMetrics.put(metric, measureComputer);
}
return this;
}

private MeasureComputer validateMeasureComputer(MeasureComputer measureComputer) {
if (measureComputer instanceof MeasureComputerImpl) {
return measureComputer;
}
// If the computer has not been created by the builder, we recreate it to make sure it's valid
Set<String> inputMetrics = measureComputer.getInputMetrics();
Set<String> outputMetrics = measureComputer.getOutputMetrics();
return newMeasureComputerBuilder()
.setInputMetrics(inputMetrics.toArray(new String[inputMetrics.size()]))
.setOutputMetrics(outputMetrics.toArray(new String[outputMetrics.size()]))
.setImplementation(measureComputer.getImplementation())
.build();
}

public List<MeasureComputer> getMeasureComputers() {
return measureComputers;
}

private void checkOutputMetricsNotAlreadyDefinedByAnotherComputer(MeasureComputer measureComputer) {
Set<String> duplicated = ImmutableSet.copyOf(Sets.intersection(computerByOutputMetrics.keySet(), measureComputer.getOutputMetrics()));
if (!duplicated.isEmpty()) {
throw new UnsupportedOperationException(generateErrorMsg(duplicated));
}
}

private String generateErrorMsg(Set<String> duplicated){
StringBuilder errorMsg = new StringBuilder();
for (String duplicationMetric : duplicated) {
MeasureComputer otherComputer = computerByOutputMetrics.get(duplicationMetric);
errorMsg.append(String.format(
"The output metric '%s' is already declared by another computer. This computer has these input metrics '%s' and these output metrics '%s'. ",
duplicationMetric, otherComputer.getInputMetrics(), otherComputer.getOutputMetrics()));
}
return errorMsg.toString();
}

@Override
public MeasureComputer.MeasureComputerBuilder newMeasureComputerBuilder() {
return new MeasureComputerImpl.MeasureComputerBuilderImpl();
}
}

+ 52
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerWrapper.java View File

@@ -0,0 +1,52 @@
/*
* 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.measure.api;

import javax.annotation.concurrent.Immutable;
import org.sonar.api.ce.measure.MeasureComputer;
import org.sonar.api.ce.measure.MeasureComputer.MeasureComputerDefinition;

import static java.util.Objects.requireNonNull;

@Immutable
public class MeasureComputerWrapper {

private final MeasureComputer computer;
private final MeasureComputerDefinition definition;

public MeasureComputerWrapper(MeasureComputer computer, MeasureComputerDefinition definition) {
this.computer = requireNonNull(computer);
this.definition = requireNonNull(definition);
}

public MeasureComputer getComputer() {
return computer;
}

public MeasureComputerDefinition getDefinition() {
return definition;
}

@Override
public String toString() {
return computer.toString();
}
}

+ 114
- 52
server/sonar-server/src/main/java/org/sonar/server/computation/step/FeedMeasureComputers.java View File

@@ -21,9 +21,10 @@
package org.sonar.server.computation.step;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -31,124 +32,118 @@ import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.sonar.api.ce.measure.MeasureComputer;
import org.sonar.api.ce.measure.MeasureComputerProvider;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Metric;
import org.sonar.api.measures.Metrics;
import org.sonar.api.utils.dag.DirectAcyclicGraph;
import org.sonar.server.computation.measure.MutableMeasureComputersHolder;
import org.sonar.server.computation.measure.api.MeasureComputerProviderContext;
import org.sonar.server.computation.measure.api.MeasureComputerDefinitionImpl;
import org.sonar.server.computation.measure.api.MeasureComputerWrapper;

import static com.google.common.collect.FluentIterable.from;
import static org.sonar.api.ce.measure.MeasureComputer.MeasureComputerDefinition;
import static org.sonar.api.ce.measure.MeasureComputer.MeasureComputerDefinitionContext;

public class FeedMeasureComputers implements ComputationStep {

private static final Set<String> CORE_METRIC_KEYS = FluentIterable.from(CoreMetrics.getMetrics()).transform(MetricToKey.INSTANCE).toSet();
private static final Set<String> CORE_METRIC_KEYS = from(CoreMetrics.getMetrics()).transform(MetricToKey.INSTANCE).toSet();
private Set<String> pluginMetricKeys;

private final MutableMeasureComputersHolder measureComputersHolder;
private final Metrics[] metricsRepositories;
private final MeasureComputerProvider[] measureComputerProviders;
private final MeasureComputer[] measureComputers;

public FeedMeasureComputers(MutableMeasureComputersHolder measureComputersHolder, Metrics[] metricsRepositories, MeasureComputerProvider[] measureComputerProviders) {
public FeedMeasureComputers(MutableMeasureComputersHolder measureComputersHolder, Metrics[] metricsRepositories, MeasureComputer[] measureComputers) {
this.measureComputersHolder = measureComputersHolder;
this.measureComputerProviders = measureComputerProviders;
this.metricsRepositories = metricsRepositories;
this.measureComputers = measureComputers;
this.pluginMetricKeys = from(Arrays.asList(metricsRepositories))
.transformAndConcat(MetricsToMetricList.INSTANCE)
.transform(MetricToKey.INSTANCE)
.toSet();
}

/**
* Constructor override used by Pico to instantiate the class when no plugin is defining metrics
*/
public FeedMeasureComputers(MutableMeasureComputersHolder measureComputersHolder, MeasureComputerProvider[] measureComputerProviders) {
this(measureComputersHolder, new Metrics[] {}, measureComputerProviders);
public FeedMeasureComputers(MutableMeasureComputersHolder measureComputersHolder, MeasureComputer[] measureComputers) {
this(measureComputersHolder, new Metrics[] {}, measureComputers);
}

/**
* Constructor override used by Pico to instantiate the class when no plugin is defining measure computers
*/
public FeedMeasureComputers(MutableMeasureComputersHolder measureComputersHolder, Metrics[] metricsRepositories) {
this(measureComputersHolder, metricsRepositories, new MeasureComputerProvider[] {});
this(measureComputersHolder, metricsRepositories, new MeasureComputer[]{});
}

/**
* Constructor override used by Pico to instantiate the class when no plugin is defining metrics neither measure computers
*/
public FeedMeasureComputers(MutableMeasureComputersHolder measureComputersHolder) {
this(measureComputersHolder, new Metrics[] {}, new MeasureComputerProvider[] {});
this(measureComputersHolder, new Metrics[] {}, new MeasureComputer[] {});
}

@Override
public void execute() {
MeasureComputerProviderContext context = new MeasureComputerProviderContext();
for (MeasureComputerProvider provider : measureComputerProviders) {
provider.register(context);
}
validateInputMetrics(context.getMeasureComputers());
measureComputersHolder.setMeasureComputers(sortComputers(context.getMeasureComputers()));
List<MeasureComputerWrapper> wrappers = from(Arrays.asList(measureComputers)).transform(ToMeasureWrapper.INSTANCE).toList();
validateMetrics(wrappers);
measureComputersHolder.setMeasureComputers(sortComputers(wrappers));
}

private static Iterable<MeasureComputer> sortComputers(List<MeasureComputer> computers) {
Map<String, MeasureComputer> computersByOutputMetric = new HashMap<>();
Map<String, MeasureComputer> computersByInputMetric = new HashMap<>();
feedComputersByMetric(computers, computersByOutputMetric, computersByInputMetric);
private static Iterable<MeasureComputerWrapper> sortComputers(List<MeasureComputerWrapper> wrappers) {
Map<String, MeasureComputerWrapper> computersByOutputMetric = new HashMap<>();
Map<String, MeasureComputerWrapper> computersByInputMetric = new HashMap<>();
feedComputersByMetric(wrappers, computersByOutputMetric, computersByInputMetric);
ToComputerByKey toComputerByOutputMetricKey = new ToComputerByKey(computersByOutputMetric);
ToComputerByKey toComputerByInputMetricKey = new ToComputerByKey(computersByInputMetric);

DirectAcyclicGraph dag = new DirectAcyclicGraph();
for (MeasureComputer computer : computers) {
for (MeasureComputerWrapper computer : wrappers) {
dag.add(computer);
for (MeasureComputer dependency : getDependencies(computer, toComputerByOutputMetricKey)) {
for (MeasureComputerWrapper dependency : getDependencies(computer, toComputerByOutputMetricKey)) {
dag.add(computer, dependency);
}
for (MeasureComputer generates : getDependents(computer, toComputerByInputMetricKey)) {
for (MeasureComputerWrapper generates : getDependents(computer, toComputerByInputMetricKey)) {
dag.add(generates, computer);
}
}
return dag.sort();
}

private static void feedComputersByMetric(List<MeasureComputer> computers, Map<String, MeasureComputer> computersByOutputMetric,
Map<String, MeasureComputer> computersByInputMetric) {
for (MeasureComputer computer : computers) {
for (String outputMetric : computer.getOutputMetrics()) {
private static void feedComputersByMetric(List<MeasureComputerWrapper> wrappers, Map<String, MeasureComputerWrapper> computersByOutputMetric,
Map<String, MeasureComputerWrapper> computersByInputMetric) {
for (MeasureComputerWrapper computer : wrappers) {
for (String outputMetric : computer.getDefinition().getOutputMetrics()) {
computersByOutputMetric.put(outputMetric, computer);
}
for (String inputMetric : computer.getInputMetrics()) {
for (String inputMetric : computer.getDefinition().getInputMetrics()) {
computersByInputMetric.put(inputMetric, computer);
}
}
}

private void validateInputMetrics(List<MeasureComputer> computers) {
Set<String> pluginMetricKeys = FluentIterable.from(Arrays.asList(metricsRepositories))
.transformAndConcat(MetricsToMetricList.INSTANCE)
.transform(MetricToKey.INSTANCE)
.toSet();
// TODO would be nice to generate an error containing all bad input metrics
for (MeasureComputer computer : computers) {
for (String metric : computer.getInputMetrics()) {
if (!pluginMetricKeys.contains(metric) && !CORE_METRIC_KEYS.contains(metric)) {
throw new IllegalStateException(String.format("Metric '%s' cannot be used as an input metric as it's no a core metric and no plugin declare this metric", metric));
}
}
}
private void validateMetrics(List<MeasureComputerWrapper> wrappers) {
from(wrappers).transformAndConcat(ToInputMetrics.INSTANCE).filter(new ValidateInputMetric()).size();
from(wrappers).transformAndConcat(ToOutputMetrics.INSTANCE).filter(new ValidateOutputMetric()).size();
}

private static Iterable<MeasureComputer> getDependencies(MeasureComputer measureComputer, ToComputerByKey toComputerByOutputMetricKey) {
private static Iterable<MeasureComputerWrapper> getDependencies(MeasureComputerWrapper measureComputer, ToComputerByKey toComputerByOutputMetricKey) {
// Remove null computer because a computer can depend on a metric that is only generated by a sensor or on a core metrics
return FluentIterable.from(measureComputer.getInputMetrics()).transform(toComputerByOutputMetricKey).filter(Predicates.notNull());
return from(measureComputer.getDefinition().getInputMetrics()).transform(toComputerByOutputMetricKey).filter(Predicates.notNull());
}

private static Iterable<MeasureComputer> getDependents(MeasureComputer measureComputer, ToComputerByKey toComputerByInputMetricKey) {
return FluentIterable.from(measureComputer.getInputMetrics()).transform(toComputerByInputMetricKey);
private static Iterable<MeasureComputerWrapper> getDependents(MeasureComputerWrapper measureComputer, ToComputerByKey toComputerByInputMetricKey) {
return from(measureComputer.getDefinition().getInputMetrics()).transform(toComputerByInputMetricKey);
}

private static class ToComputerByKey implements Function<String, MeasureComputer> {
private final Map<String, MeasureComputer> computersByMetric;
private static class ToComputerByKey implements Function<String, MeasureComputerWrapper> {
private final Map<String, MeasureComputerWrapper> computersByMetric;

private ToComputerByKey(Map<String, MeasureComputer> computersByMetric) {
private ToComputerByKey(Map<String, MeasureComputerWrapper> computersByMetric) {
this.computersByMetric = computersByMetric;
}

@Override
public MeasureComputer apply(@Nonnull String metricKey) {
public MeasureComputerWrapper apply(@Nonnull String metricKey) {
return computersByMetric.get(metricKey);
}
}
@@ -172,6 +167,73 @@ public class FeedMeasureComputers implements ComputationStep {
}
}

private enum ToMeasureWrapper implements Function<MeasureComputer, MeasureComputerWrapper> {
INSTANCE;

private final MeasureComputerDefinitionContextImpl context = new MeasureComputerDefinitionContextImpl();

@Override
public MeasureComputerWrapper apply(@Nonnull MeasureComputer measureComputer) {
MeasureComputerDefinition def = measureComputer.define(context);
// If the computer has not been created by the builder, we recreate it to make sure it's valid
Set<String> inputMetrics = def.getInputMetrics();
Set<String> outputMetrics = def.getOutputMetrics();
MeasureComputerDefinition validateDef = new MeasureComputerDefinitionImpl.BuilderImpl()
.setInputMetrics(from(inputMetrics).toArray(String.class))
.setOutputMetrics(from(outputMetrics).toArray(String.class))
.build();
return new MeasureComputerWrapper(measureComputer, validateDef);
}
}

private enum ToInputMetrics implements Function<MeasureComputerWrapper, Collection<String>> {
INSTANCE;

@Override
public Collection<String> apply(@Nonnull MeasureComputerWrapper input) {
return input.getDefinition().getInputMetrics();
}
}

private class ValidateInputMetric implements Predicate<String> {
@Override
public boolean apply(@Nullable String metric) {
if (!pluginMetricKeys.contains(metric) && !CORE_METRIC_KEYS.contains(metric)) {
throw new IllegalStateException(String.format("Metric '%s' cannot be used as an input metric as it's not a core metric and no plugin declare this metric", metric));
}
return true;
}
}

private enum ToOutputMetrics implements Function<MeasureComputerWrapper, Collection<String>> {
INSTANCE;

@Override
public Collection<String> apply(@Nonnull MeasureComputerWrapper input) {
return input.getDefinition().getOutputMetrics();
}
}

private class ValidateOutputMetric implements Predicate<String> {
@Override
public boolean apply(@Nullable String metric) {
if (CORE_METRIC_KEYS.contains(metric)) {
throw new IllegalStateException(String.format("Metric '%s' cannot be used as an output metric as it's a core metric", metric));
}
if (!pluginMetricKeys.contains(metric)) {
throw new IllegalStateException(String.format("Metric '%s' cannot be used as an output metric as no plugin declare this metric", metric));
}
return true;
}
}

private static class MeasureComputerDefinitionContextImpl implements MeasureComputerDefinitionContext {
@Override
public MeasureComputerDefinition.Builder newDefinitionBuilder() {
return new MeasureComputerDefinitionImpl.BuilderImpl();
}
}

@Override
public String getDescription() {
return "Feed measure computers";

+ 4
- 4
server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureComputersHolderImplTest.java View File

@@ -25,7 +25,7 @@ import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.ce.measure.MeasureComputer;
import org.sonar.server.computation.measure.api.MeasureComputerWrapper;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -39,7 +39,7 @@ public class MeasureComputersHolderImplTest {

@Test
public void get_measure_computers() throws Exception {
MeasureComputer measureComputer = mock(MeasureComputer.class);
MeasureComputerWrapper measureComputer = mock(MeasureComputerWrapper.class);
underTest.setMeasureComputers(Collections.singletonList(measureComputer));

assertThat(underTest.getMeasureComputers()).containsOnly(measureComputer);
@@ -55,7 +55,7 @@ public class MeasureComputersHolderImplTest {

@Test
public void set_measure_computers_supports_empty_arg_is_empty() {
underTest.setMeasureComputers(ImmutableList.<MeasureComputer>of());
underTest.setMeasureComputers(ImmutableList.<MeasureComputerWrapper>of());

assertThat(underTest.getMeasureComputers()).isEmpty();
}
@@ -65,7 +65,7 @@ public class MeasureComputersHolderImplTest {
thrown.expect(IllegalStateException.class);
thrown.expectMessage("Measure computers have already been initialized");

MeasureComputer measureComputer = mock(MeasureComputer.class);
MeasureComputerWrapper measureComputer = mock(MeasureComputerWrapper.class);
underTest.setMeasureComputers(Collections.singletonList(measureComputer));
underTest.setMeasureComputers(Collections.singletonList(measureComputer));
}

+ 27
- 19
server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureComputersVisitorTest.java View File

@@ -30,7 +30,8 @@ import org.sonar.server.computation.component.Component;
import org.sonar.server.computation.component.ComponentVisitor;
import org.sonar.server.computation.component.VisitorsCrawler;
import org.sonar.server.computation.issue.ComponentIssuesRepository;
import org.sonar.server.computation.measure.api.MeasureComputerImpl;
import org.sonar.server.computation.measure.api.MeasureComputerDefinitionImpl;
import org.sonar.server.computation.measure.api.MeasureComputerWrapper;
import org.sonar.server.computation.metric.MetricRepositoryRule;

import static com.google.common.collect.Lists.newArrayList;
@@ -105,26 +106,32 @@ public class MeasureComputersVisitorTest {
measureRepository.addRawMeasure(ROOT_REF, NCLOC_KEY, newMeasureBuilder().create(50));
measureRepository.addRawMeasure(ROOT_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(7));

final MeasureComputer.MeasureComputerDefinition definition = new MeasureComputerDefinitionImpl.BuilderImpl()
.setInputMetrics(NCLOC_KEY, COMMENT_LINES_KEY)
.setOutputMetrics(NEW_METRIC_KEY)
.build();
measureComputersHolder.setMeasureComputers(newArrayList(
new MeasureComputerImpl.MeasureComputerBuilderImpl()
.setInputMetrics(NCLOC_KEY, COMMENT_LINES_KEY)
.setOutputMetrics(NEW_METRIC_KEY)
.setImplementation(
new MeasureComputer.Implementation() {
@Override
public void compute(Context ctx) {
org.sonar.api.ce.measure.Measure ncloc = ctx.getMeasure(NCLOC_KEY);
org.sonar.api.ce.measure.Measure comment = ctx.getMeasure(COMMENT_LINES_KEY);
if (ncloc != null && comment != null) {
ctx.addMeasure(NEW_METRIC_KEY, ncloc.getIntValue() + comment.getIntValue());
}
new MeasureComputerWrapper(
new MeasureComputer() {
@Override
public MeasureComputerDefinition define(MeasureComputerDefinitionContext defContext) {
return definition;
}

@Override
public void compute(MeasureComputerContext context) {
org.sonar.api.ce.measure.Measure ncloc = context.getMeasure(NCLOC_KEY);
org.sonar.api.ce.measure.Measure comment = context.getMeasure(COMMENT_LINES_KEY);
if (ncloc != null && comment != null) {
context.addMeasure(NEW_METRIC_KEY, ncloc.getIntValue() + comment.getIntValue());
}
}
)
.build()
));
},
definition
)));

VisitorsCrawler visitorsCrawler = new VisitorsCrawler(Arrays.<ComponentVisitor>asList(new MeasureComputersVisitor(metricRepository, measureRepository, null, measureComputersHolder, componentIssuesRepository)));
VisitorsCrawler visitorsCrawler = new VisitorsCrawler(Arrays.<ComponentVisitor>asList(new MeasureComputersVisitor(metricRepository, measureRepository, null,
measureComputersHolder, componentIssuesRepository)));
visitorsCrawler.visit(ROOT);

assertThat(toEntries(measureRepository.getAddedRawMeasures(FILE_1_REF))).containsOnly(entryOf(NEW_METRIC_KEY, newMeasureBuilder().create(12)));
@@ -147,8 +154,9 @@ public class MeasureComputersVisitorTest {
measureRepository.addRawMeasure(ROOT_REF, NCLOC_KEY, newMeasureBuilder().create(50));
measureRepository.addRawMeasure(ROOT_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(7));

measureComputersHolder.setMeasureComputers(Collections.<MeasureComputer>emptyList());
VisitorsCrawler visitorsCrawler = new VisitorsCrawler(Arrays.<ComponentVisitor>asList(new MeasureComputersVisitor(metricRepository, measureRepository, null, measureComputersHolder, componentIssuesRepository)));
measureComputersHolder.setMeasureComputers(Collections.<MeasureComputerWrapper>emptyList());
VisitorsCrawler visitorsCrawler = new VisitorsCrawler(Arrays.<ComponentVisitor>asList(new MeasureComputersVisitor(metricRepository, measureRepository, null,
measureComputersHolder, componentIssuesRepository)));
visitorsCrawler.visit(ROOT);

assertThat(toEntries(measureRepository.getAddedRawMeasures(FILE_1_REF))).isEmpty();

server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContextTest.java → server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerContextImplTest.java View File

@@ -24,7 +24,6 @@ import com.google.common.base.Optional;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -43,7 +42,6 @@ import org.sonar.server.computation.metric.Metric;
import org.sonar.server.computation.metric.MetricImpl;
import org.sonar.server.computation.metric.MetricRepositoryRule;

import static com.google.common.collect.ImmutableSet.of;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.guava.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -53,7 +51,7 @@ import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
import static org.sonar.server.computation.component.ReportComponent.builder;
import static org.sonar.server.computation.measure.Measure.newMeasureBuilder;

public class MeasureComputerImplementationContextTest {
public class MeasureComputerContextImplTest {

@Rule
public ExpectedException thrown = ExpectedException.none();
@@ -98,7 +96,7 @@ public class MeasureComputerImplementationContextTest {

@Test
public void get_component() throws Exception {
MeasureComputer.Implementation.Context underTest = newContext(FILE_1_REF);
MeasureComputerContextImpl underTest = newContext(FILE_1_REF);
assertThat(underTest.getComponent().getType()).isEqualTo(Component.Type.FILE);
}

@@ -108,7 +106,7 @@ public class MeasureComputerImplementationContextTest {
serverSettings.setProperty("prop", "value");
when(settingsRepository.getSettings(FILE_1)).thenReturn(serverSettings);

MeasureComputer.Implementation.Context underTest = newContext(FILE_1_REF);
MeasureComputerContextImpl underTest = newContext(FILE_1_REF);
assertThat(underTest.getSettings().getString("prop")).isEqualTo("value");
assertThat(underTest.getSettings().getString("unknown")).isNull();
}
@@ -119,7 +117,7 @@ public class MeasureComputerImplementationContextTest {
serverSettings.setProperty("prop", "1,3.4,8,50");
when(settingsRepository.getSettings(FILE_1)).thenReturn(serverSettings);

MeasureComputer.Implementation.Context underTest = newContext(FILE_1_REF);
MeasureComputerContextImpl underTest = newContext(FILE_1_REF);
assertThat(underTest.getSettings().getStringArray("prop")).containsExactly("1", "3.4", "8", "50");
assertThat(underTest.getSettings().getStringArray("unknown")).isEmpty();
}
@@ -128,7 +126,7 @@ public class MeasureComputerImplementationContextTest {
public void get_measure() throws Exception {
measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(10));

MeasureComputer.Implementation.Context underTest = newContext(FILE_1_REF, of(NCLOC_KEY), of(COMMENT_LINES_KEY));
MeasureComputerContextImpl underTest = newContext(FILE_1_REF, NCLOC_KEY, COMMENT_LINES_KEY);
assertThat(underTest.getMeasure(NCLOC_KEY).getIntValue()).isEqualTo(10);
}

@@ -137,7 +135,7 @@ public class MeasureComputerImplementationContextTest {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Only metrics in [another metric] can be used to load measures");

MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of("another metric"), of("debt"));
MeasureComputerContextImpl underTest = newContext(PROJECT_REF, "another metric", "debt");
underTest.getMeasure(NCLOC_KEY);
}

@@ -146,7 +144,7 @@ public class MeasureComputerImplementationContextTest {
measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(10));
measureRepository.addRawMeasure(FILE_2_REF, NCLOC_KEY, newMeasureBuilder().create(12));

MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of(NCLOC_KEY), of(COMMENT_LINES_KEY));
MeasureComputerContextImpl underTest = newContext(PROJECT_REF, NCLOC_KEY, COMMENT_LINES_KEY);
assertThat(underTest.getChildrenMeasures(NCLOC_KEY)).hasSize(2);
assertThat(underTest.getChildrenMeasures(NCLOC_KEY)).extracting("intValue").containsOnly(10, 12);
}
@@ -156,7 +154,7 @@ public class MeasureComputerImplementationContextTest {
measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(10));
// No data on file 2

MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of(NCLOC_KEY), of(COMMENT_LINES_KEY));
MeasureComputerContextImpl underTest = newContext(PROJECT_REF, NCLOC_KEY, COMMENT_LINES_KEY);
assertThat(underTest.getChildrenMeasures(NCLOC_KEY)).extracting("intValue").containsOnly(10);
}

@@ -164,7 +162,7 @@ public class MeasureComputerImplementationContextTest {
public void not_fail_to_get_children_measures_on_output_metric() throws Exception {
measureRepository.addRawMeasure(FILE_1_REF, INT_METRIC_KEY, newMeasureBuilder().create(10));

MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of(NCLOC_KEY), of(INT_METRIC_KEY));
MeasureComputerContextImpl underTest = newContext(PROJECT_REF, NCLOC_KEY, INT_METRIC_KEY);
assertThat(underTest.getChildrenMeasures(INT_METRIC_KEY)).hasSize(1);
assertThat(underTest.getChildrenMeasures(INT_METRIC_KEY)).extracting("intValue").containsOnly(10);
}
@@ -174,13 +172,13 @@ public class MeasureComputerImplementationContextTest {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Only metrics in [another metric] can be used to load measures");

MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of("another metric"), of("debt"));
MeasureComputerContextImpl underTest = newContext(PROJECT_REF, "another metric", "debt");
underTest.getChildrenMeasures(NCLOC_KEY);
}

@Test
public void add_int_measure_create_measure_of_type_int_with_right_value() throws Exception {
MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of(NCLOC_KEY), of(INT_METRIC_KEY));
MeasureComputerContextImpl underTest = newContext(PROJECT_REF, NCLOC_KEY, INT_METRIC_KEY);
underTest.addMeasure(INT_METRIC_KEY, 10);

Optional<Measure> measure = measureRepository.getAddedRawMeasure(PROJECT_REF, INT_METRIC_KEY);
@@ -190,7 +188,7 @@ public class MeasureComputerImplementationContextTest {

@Test
public void add_double_measure_create_measure_of_type_double_with_right_value() throws Exception {
MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of(NCLOC_KEY), of(DOUBLE_METRIC_KEY));
MeasureComputerContextImpl underTest = newContext(PROJECT_REF, NCLOC_KEY, DOUBLE_METRIC_KEY);
underTest.addMeasure(DOUBLE_METRIC_KEY, 10d);

Optional<Measure> measure = measureRepository.getAddedRawMeasure(PROJECT_REF, DOUBLE_METRIC_KEY);
@@ -200,7 +198,7 @@ public class MeasureComputerImplementationContextTest {

@Test
public void add_long_measure_create_measure_of_type_long_with_right_value() throws Exception {
MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of(NCLOC_KEY), of(LONG_METRIC_KEY));
MeasureComputerContextImpl underTest = newContext(PROJECT_REF, NCLOC_KEY, LONG_METRIC_KEY);
underTest.addMeasure(LONG_METRIC_KEY, 10L);

Optional<Measure> measure = measureRepository.getAddedRawMeasure(PROJECT_REF, LONG_METRIC_KEY);
@@ -210,7 +208,7 @@ public class MeasureComputerImplementationContextTest {

@Test
public void add_string_measure_create_measure_of_type_string_with_right_value() throws Exception {
MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of(NCLOC_KEY), of(STRING_METRIC_KEY));
MeasureComputerContextImpl underTest = newContext(PROJECT_REF, NCLOC_KEY, STRING_METRIC_KEY);
underTest.addMeasure(STRING_METRIC_KEY, "data");

Optional<Measure> measure = measureRepository.getAddedRawMeasure(PROJECT_REF, STRING_METRIC_KEY);
@@ -223,7 +221,7 @@ public class MeasureComputerImplementationContextTest {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Only metrics in [int_metric_key] can be used to add measures. Metric 'double_metric_key' is not allowed.");

MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of(NCLOC_KEY), of(INT_METRIC_KEY));
MeasureComputerContextImpl underTest = newContext(PROJECT_REF, NCLOC_KEY, INT_METRIC_KEY);
underTest.addMeasure(DOUBLE_METRIC_KEY, 10);
}

@@ -234,7 +232,7 @@ public class MeasureComputerImplementationContextTest {

measureRepository.addRawMeasure(FILE_1_REF, INT_METRIC_KEY, newMeasureBuilder().create(20));

MeasureComputer.Implementation.Context underTest = newContext(FILE_1_REF, of(NCLOC_KEY), of(INT_METRIC_KEY));
MeasureComputerContextImpl underTest = newContext(FILE_1_REF, NCLOC_KEY, INT_METRIC_KEY);
underTest.addMeasure(INT_METRIC_KEY, 10);
}

@@ -248,7 +246,7 @@ public class MeasureComputerImplementationContextTest {
.setResolution("FIXED")
.setDebt(Duration.create(10l));

MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, Arrays.asList(issue));
MeasureComputerContextImpl underTest = newContext(PROJECT_REF, Arrays.asList(issue));

assertThat(underTest.getIssues()).hasSize(1);
org.sonar.api.ce.measure.Issue result = underTest.getIssues().get(0);
@@ -260,37 +258,29 @@ public class MeasureComputerImplementationContextTest {
assertThat(result.debt()).isEqualTo(Duration.create(10l));
}

private MeasureComputer.Implementation.Context newContext(int componentRef) {
return newContext(componentRef, Collections.<String>emptySet(), Collections.<String>emptySet());
private MeasureComputerContextImpl newContext(int componentRef) {
return newContext(componentRef, NCLOC_KEY, COMMENT_LINES_KEY, Collections.<DefaultIssue>emptyList());
}

private MeasureComputer.Implementation.Context newContext(int componentRef, List<DefaultIssue> issues) {
return newContext(componentRef, Collections.<String>emptySet(), Collections.<String>emptySet(), issues);
private MeasureComputerContextImpl newContext(int componentRef, List<DefaultIssue> issues) {
return newContext(componentRef, NCLOC_KEY, COMMENT_LINES_KEY, issues);
}

private MeasureComputer.Implementation.Context newContext(int componentRef, final Set<String> inputMetrics, final Set<String> outputMetrics) {
return newContext(componentRef, inputMetrics, outputMetrics, Collections.<DefaultIssue>emptyList());
private MeasureComputerContextImpl newContext(int componentRef, String inputMetric, String outputMetric) {
return newContext(componentRef, inputMetric, outputMetric, Collections.<DefaultIssue>emptyList());
}

private MeasureComputer.Implementation.Context newContext(int componentRef, final Set<String> inputMetrics, final Set<String> outputMetrics, List<DefaultIssue> issues) {
private MeasureComputerContextImpl newContext(int componentRef, String inputMetric, String outputMetric, List<DefaultIssue> issues) {
componentIssuesRepository.setIssues(componentRef, issues);
MeasureComputer measureComputer = new MeasureComputer() {
@Override
public Set<String> getInputMetrics() {
return inputMetrics;
}

@Override
public Set<String> getOutputMetrics() {
return outputMetrics;
}

@Override
public Implementation getImplementation() {
return null;
}
};
return new MeasureComputerImplementationContext(treeRootHolder.getComponentByRef(componentRef), measureComputer,

MeasureComputer.MeasureComputerDefinition definition = new MeasureComputerDefinitionImpl.BuilderImpl()
.setInputMetrics(new String[] {inputMetric})
.setOutputMetrics(new String[] {outputMetric})
.build();

MeasureComputerContextImpl context = new MeasureComputerContextImpl(treeRootHolder.getComponentByRef(componentRef),
settingsRepository, measureRepository, metricRepository, componentIssuesRepository);
context.setDefinition(definition);
return context;
}
}

server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplTest.java → server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerDefinitionImplTest.java View File

@@ -27,59 +27,42 @@ import org.sonar.api.ce.measure.MeasureComputer;

import static org.assertj.core.api.Assertions.assertThat;

public class MeasureComputerImplTest {

private static final MeasureComputer.Implementation DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION = new MeasureComputer.Implementation() {
@Override
public void compute(MeasureComputer.Implementation.Context ctx) {
// Nothing here for this test
}

@Override
public String toString() {
return "Test implementation";
}
};
public class MeasureComputerDefinitionImplTest {

@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void build_measure_computer() throws Exception {
public void build_measure_computer_definition() throws Exception {
String inputMetric = "ncloc";
String outputMetric = "comment_density";
MeasureComputer measureComputer = new MeasureComputerImpl.MeasureComputerBuilderImpl()
MeasureComputer.MeasureComputerDefinition measureComputer = new MeasureComputerDefinitionImpl.BuilderImpl()
.setInputMetrics(inputMetric)
.setOutputMetrics(outputMetric)
.setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION)
.build();

assertThat(measureComputer.getInputMetrics()).containsOnly(inputMetric);
assertThat(measureComputer.getOutputMetrics()).containsOnly(outputMetric);
assertThat(measureComputer.getImplementation()).isEqualTo(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION);
}

@Test
public void build_measure_computer_with_multiple_metrics() throws Exception {
String[] inputMetrics = {"ncloc", "comment"};
String[] outputMetrics = {"comment_density_1", "comment_density_2"};
MeasureComputer measureComputer = new MeasureComputerImpl.MeasureComputerBuilderImpl()
MeasureComputer.MeasureComputerDefinition measureComputer = new MeasureComputerDefinitionImpl.BuilderImpl()
.setInputMetrics(inputMetrics)
.setOutputMetrics(outputMetrics)
.setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION)
.build();

assertThat(measureComputer.getInputMetrics()).containsOnly(inputMetrics);
assertThat(measureComputer.getOutputMetrics()).containsOnly(outputMetrics);
assertThat(measureComputer.getImplementation()).isEqualTo(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION);
}

@Test
public void input_metrics_can_be_empty() throws Exception {
MeasureComputer measureComputer = new MeasureComputerImpl.MeasureComputerBuilderImpl()
MeasureComputer.MeasureComputerDefinition measureComputer = new MeasureComputerDefinitionImpl.BuilderImpl()
.setInputMetrics()
.setOutputMetrics("comment_density_1", "comment_density_2")
.setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION)
.build();

assertThat(measureComputer.getInputMetrics()).isEmpty();
@@ -87,9 +70,8 @@ public class MeasureComputerImplTest {

@Test
public void input_metrics_is_empty_when_not_set() throws Exception {
MeasureComputer measureComputer = new MeasureComputerImpl.MeasureComputerBuilderImpl()
MeasureComputer.MeasureComputerDefinition measureComputer = new MeasureComputerDefinitionImpl.BuilderImpl()
.setOutputMetrics("comment_density_1", "comment_density_2")
.setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION)
.build();

assertThat(measureComputer.getInputMetrics()).isEmpty();
@@ -100,10 +82,9 @@ public class MeasureComputerImplTest {
thrown.expect(NullPointerException.class);
thrown.expectMessage("Input metrics cannot be null");

new MeasureComputerImpl.MeasureComputerBuilderImpl()
new MeasureComputerDefinitionImpl.BuilderImpl()
.setInputMetrics(null)
.setOutputMetrics("comment_density_1", "comment_density_2")
.setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION);
.setOutputMetrics("comment_density_1", "comment_density_2");
}

@Test
@@ -111,32 +92,29 @@ public class MeasureComputerImplTest {
thrown.expect(NullPointerException.class);
thrown.expectMessage("Null metric is not allowed");

new MeasureComputerImpl.MeasureComputerBuilderImpl()
new MeasureComputerDefinitionImpl.BuilderImpl()
.setInputMetrics("ncloc", null)
.setOutputMetrics("comment_density_1", "comment_density_2")
.setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION);
.setOutputMetrics("comment_density_1", "comment_density_2");
}

@Test
public void fail_with_IAE_when_no_output_metrics() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("At least one output metric must be defined");
public void fail_with_NPE_when_no_output_metrics() throws Exception {
thrown.expect(NullPointerException.class);
thrown.expectMessage("Output metrics cannot be null");

new MeasureComputerImpl.MeasureComputerBuilderImpl()
new MeasureComputerDefinitionImpl.BuilderImpl()
.setInputMetrics("ncloc", "comment")
.setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION)
.build();
}

@Test
public void fail_with_IAE_when_null_output_metrics() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("At least one output metric must be defined");
public void fail_with_NPE_when_null_output_metrics() throws Exception {
thrown.expect(NullPointerException.class);
thrown.expectMessage("Output metrics cannot be null");

new MeasureComputerImpl.MeasureComputerBuilderImpl()
new MeasureComputerDefinitionImpl.BuilderImpl()
.setInputMetrics("ncloc", "comment")
.setOutputMetrics(null)
.setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION);
.setOutputMetrics(null);
}

@Test
@@ -144,10 +122,9 @@ public class MeasureComputerImplTest {
thrown.expect(NullPointerException.class);
thrown.expectMessage("Null metric is not allowed");

new MeasureComputerImpl.MeasureComputerBuilderImpl()
new MeasureComputerDefinitionImpl.BuilderImpl()
.setInputMetrics("ncloc", "comment")
.setOutputMetrics("comment_density_1", null)
.setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION);
.setOutputMetrics("comment_density_1", null);
}

@Test
@@ -155,52 +132,26 @@ public class MeasureComputerImplTest {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("At least one output metric must be defined");

new MeasureComputerImpl.MeasureComputerBuilderImpl()
.setInputMetrics("ncloc", "comment")
.setOutputMetrics()
.setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION);
}

@Test
public void fail_with_IAE_when_no_implementation() throws Exception {
thrown.expect(NullPointerException.class);
thrown.expectMessage("The implementation is missing");

new MeasureComputerImpl.MeasureComputerBuilderImpl()
new MeasureComputerDefinitionImpl.BuilderImpl()
.setInputMetrics("ncloc", "comment")
.setOutputMetrics("comment_density_1", "comment_density_2")
.build();
}

@Test
public void fail_with_IAE_when_null_implementation() throws Exception {
thrown.expect(NullPointerException.class);
thrown.expectMessage("The implementation is missing");

new MeasureComputerImpl.MeasureComputerBuilderImpl()
.setInputMetrics("ncloc", "comment")
.setOutputMetrics("comment_density_1", "comment_density_2")
.setImplementation(null);
.setOutputMetrics();
}

@Test
public void test_equals_and_hashcode() throws Exception {
MeasureComputer computer = new MeasureComputerImpl.MeasureComputerBuilderImpl()
MeasureComputer.MeasureComputerDefinition computer = new MeasureComputerDefinitionImpl.BuilderImpl()
.setInputMetrics("ncloc", "comment")
.setOutputMetrics("comment_density_1", "comment_density_2")
.setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION)
.build();

MeasureComputer sameComputer = new MeasureComputerImpl.MeasureComputerBuilderImpl()
MeasureComputer.MeasureComputerDefinition sameComputer = new MeasureComputerDefinitionImpl.BuilderImpl()
.setInputMetrics("comment", "ncloc")
.setOutputMetrics("comment_density_2", "comment_density_1")
.setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION)
.build();

MeasureComputer anotherComputer = new MeasureComputerImpl.MeasureComputerBuilderImpl()
MeasureComputer.MeasureComputerDefinition anotherComputer = new MeasureComputerDefinitionImpl.BuilderImpl()
.setInputMetrics("comment")
.setOutputMetrics("debt")
.setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION)
.build();

assertThat(computer).isEqualTo(computer);
@@ -215,11 +166,11 @@ public class MeasureComputerImplTest {

@Test
public void test_to_string() throws Exception {
assertThat(new MeasureComputerImpl.MeasureComputerBuilderImpl()
assertThat(new MeasureComputerDefinitionImpl.BuilderImpl()
.setInputMetrics("ncloc", "comment")
.setOutputMetrics("comment_density_1", "comment_density_2")
.setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION)
.build().toString())
.isEqualTo("MeasureComputerImpl{inputMetricKeys=[ncloc, comment], outputMetrics=[comment_density_1, comment_density_2], implementation=Test implementation}");
.isEqualTo("MeasureComputerDefinitionImpl{inputMetricKeys=[ncloc, comment], outputMetrics=[comment_density_1, comment_density_2]}");
}

}

+ 0
- 153
server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContextTest.java View File

@@ -1,153 +0,0 @@
/*
* 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.measure.api;

import com.google.common.collect.ImmutableSet;
import java.util.Collections;
import java.util.Set;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.ce.measure.MeasureComputer;

import static org.assertj.core.api.Assertions.assertThat;

public class MeasureComputerProviderContextTest {

@Rule
public ExpectedException thrown = ExpectedException.none();

MeasureComputerProviderContext underTest = new MeasureComputerProviderContext();

@Test
public void return_empty_list() throws Exception {
assertThat(underTest.getMeasureComputers()).isEmpty();
}

@Test
public void add_measure_computer() throws Exception {
underTest.add(newMeasureComputer("debt_density"));

assertThat(underTest.getMeasureComputers()).hasSize(1);
}

@Test
public void add_measure_computers_sharing_same_input_metrics() throws Exception {
underTest.add(newMeasureComputer(new String[]{"ncloc"}, new String[]{"debt_density"}));
underTest.add(newMeasureComputer(new String[]{"ncloc"}, new String[]{"comment"}));

assertThat(underTest.getMeasureComputers()).hasSize(2);
}

@Test
public void fail_with_unsupported_operation_exception_when_output_metric_have_already_been_registered() throws Exception {
thrown.expect(UnsupportedOperationException.class);
thrown.expectMessage("The output metric 'debt_density' is already declared by another computer. This computer has these input metrics '[ncloc, debt]' and these output metrics '[debt_by_line, debt_density]");

underTest.add(newMeasureComputer("debt_by_line","debt_density"));
underTest.add(newMeasureComputer("total_debt", "debt_density"));
}

@Test
public void fail_with_unsupported_operation_exception_when_multiple_output_metrics_have_already_been_registered() throws Exception {
thrown.expect(UnsupportedOperationException.class);
thrown.expectMessage("The output metric 'debt_density' is already declared by another computer. This computer has these input metrics '[ncloc, debt]' and these output metrics '[debt_density]'. " +
"The output metric 'debt_by_line' is already declared by another computer. This computer has these input metrics '[ncloc, debt]' and these output metrics '[debt_by_line]");

underTest.add(newMeasureComputer("debt_by_line"));
underTest.add(newMeasureComputer("debt_density"));
underTest.add(newMeasureComputer("debt_by_line", "debt_density"));
}

@Test
public void create_measure_computer_without_using_the_builder() throws Exception {
// Create a instance of MeasureComputer without using the builder
MeasureComputer measureComputer = new MeasureComputer() {
@Override
public Set<String> getInputMetrics() {
return ImmutableSet.of("ncloc", "debt");
}

@Override
public Set<String> getOutputMetrics() {
return ImmutableSet.of("debt_density");
}

@Override
public Implementation getImplementation() {
return new MeasureComputer.Implementation() {
@Override
public void compute(Context ctx) {
}
};
}
};

underTest.add(measureComputer);
assertThat(underTest.getMeasureComputers()).hasSize(1);
}

@Test
public void fail_with_IAE_when_creating_measure_computer_without_using_the_builder_but_with_invalid_output_metrics() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("At least one output metric must be defined");

MeasureComputer measureComputer = new MeasureComputer() {
@Override
public Set<String> getInputMetrics() {
return ImmutableSet.of("ncloc", "debt");
}

@Override
public Set<String> getOutputMetrics() {
return Collections.emptySet();
}

@Override
public Implementation getImplementation() {
return new MeasureComputer.Implementation() {
@Override
public void compute(Context ctx) {}
};
}
};

underTest.add(measureComputer);
}

private MeasureComputer newMeasureComputer(String... outputMetrics) {
return newMeasureComputer(new String[]{"ncloc", "debt"}, outputMetrics);
}

private MeasureComputer newMeasureComputer(String[] inputMetrics, String[] outputMetrics) {
return new MeasureComputerImpl.MeasureComputerBuilderImpl()
.setInputMetrics(inputMetrics)
.setOutputMetrics(outputMetrics)
.setImplementation(new MeasureComputer.Implementation() {
@Override
public void compute(Context ctx) {
// Nothing to do here for this test
}
})
.build();
}

}

+ 122
- 94
server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedMeasureComputersTest.java View File

@@ -20,22 +20,24 @@

package org.sonar.server.computation.step;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.ce.measure.MeasureComputer;
import org.sonar.api.ce.measure.MeasureComputerProvider;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Metric;
import org.sonar.api.measures.Metrics;
import org.sonar.server.computation.measure.MeasureComputersHolderImpl;
import org.sonar.server.computation.measure.api.MeasureComputerWrapper;

import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.util.Arrays.array;
import static org.mockito.Mockito.mock;
import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
import static org.sonar.api.measures.Metric.ValueType.DATA;
import static org.sonar.api.measures.Metric.ValueType.FLOAT;
import static org.sonar.api.measures.Metric.ValueType.INT;
@@ -55,14 +57,8 @@ public class FeedMeasureComputersTest {

@Test
public void support_core_metrics_as_input_metrics() throws Exception {
MeasureComputer.Implementation implementation = mock(MeasureComputer.Implementation.class);
MeasureComputerProvider[] providers = new MeasureComputerProvider[] {
new NewMeasureComputerProvider(
array(CoreMetrics.NCLOC_KEY),
array(NEW_METRIC_1),
implementation),
};
ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), providers);
MeasureComputer[] computers = new MeasureComputer[] {newMeasureComputer(array(NCLOC_KEY), array(NEW_METRIC_1))};
ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), computers);
underTest.execute();

assertThat(holder.getMeasureComputers()).hasSize(1);
@@ -70,14 +66,8 @@ public class FeedMeasureComputersTest {

@Test
public void support_plugin_metrics_as_input_metrics() throws Exception {
MeasureComputer.Implementation implementation = mock(MeasureComputer.Implementation.class);
MeasureComputerProvider[] providers = new MeasureComputerProvider[] {
new NewMeasureComputerProvider(
array(NEW_METRIC_1),
array(NEW_METRIC_2),
implementation),
};
ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), providers);
MeasureComputer[] computers = new MeasureComputer[] {newMeasureComputer(array(NEW_METRIC_1), array(NEW_METRIC_2))};
ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), computers);
underTest.execute();

assertThat(holder.getMeasureComputers()).hasSize(1);
@@ -85,64 +75,78 @@ public class FeedMeasureComputersTest {

@Test
public void sort_computers() throws Exception {
MeasureComputer.Implementation implementation1 = mock(MeasureComputer.Implementation.class);
MeasureComputer.Implementation implementation2 = mock(MeasureComputer.Implementation.class);
MeasureComputer.Implementation implementation3 = mock(MeasureComputer.Implementation.class);

MeasureComputerProvider[] providers = new MeasureComputerProvider[] {
// Should be the last to be executed
new NewMeasureComputerProvider(
array(NEW_METRIC_3),
array(NEW_METRIC_4),
implementation3),
// Should be the first to be executed
new NewMeasureComputerProvider(
array(NEW_METRIC_1),
array(NEW_METRIC_2),
implementation1),
// Should be the second to be executed
new NewMeasureComputerProvider(
array(NEW_METRIC_2),
array(NEW_METRIC_3),
implementation2)
};
// Should be the last to be executed
MeasureComputer measureComputer1 = newMeasureComputer(array(NEW_METRIC_3), array(NEW_METRIC_4));
// Should be the first to be executed
MeasureComputer measureComputer2 = newMeasureComputer(array(NEW_METRIC_1), array(NEW_METRIC_2));
// Should be the second to be executed
MeasureComputer measureComputer3 = newMeasureComputer(array(NEW_METRIC_2), array(NEW_METRIC_3));
MeasureComputer[] computers = new MeasureComputer[] {measureComputer1, measureComputer2, measureComputer3};

ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), computers);
underTest.execute();

List<MeasureComputerWrapper> result = newArrayList(holder.getMeasureComputers());
assertThat(result).hasSize(3);
assertThat(result.get(0).getComputer()).isEqualTo(measureComputer2);
assertThat(result.get(1).getComputer()).isEqualTo(measureComputer3);
assertThat(result.get(2).getComputer()).isEqualTo(measureComputer1);
}

ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), providers);
@Test
public void sort_computers_when_one_computer_has_no_input_metric() throws Exception {
// Should be the last to be executed
MeasureComputer measureComputer1 = newMeasureComputer(array(NEW_METRIC_3), array(NEW_METRIC_4));
// Should be the first to be executed
MeasureComputer measureComputer2 = newMeasureComputer(new String[] {}, array(NEW_METRIC_2));
// Should be the second to be executed
MeasureComputer measureComputer3 = newMeasureComputer(array(NEW_METRIC_2), array(NEW_METRIC_3));
MeasureComputer[] computers = new MeasureComputer[] {measureComputer1, measureComputer2, measureComputer3};

ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), computers);
underTest.execute();

List<MeasureComputer> computers = newArrayList(holder.getMeasureComputers());
assertThat(computers).hasSize(3);
assertThat(computers.get(0).getImplementation()).isEqualTo(implementation1);
assertThat(computers.get(1).getImplementation()).isEqualTo(implementation2);
assertThat(computers.get(2).getImplementation()).isEqualTo(implementation3);
List<MeasureComputerWrapper> result = newArrayList(holder.getMeasureComputers());
assertThat(result).hasSize(3);
assertThat(result.get(0).getComputer()).isEqualTo(measureComputer2);
assertThat(result.get(1).getComputer()).isEqualTo(measureComputer3);
assertThat(result.get(2).getComputer()).isEqualTo(measureComputer1);
}

@Test
public void fail_with_ISE_when_input_metric_is_unknown() throws Exception {
thrown.expect(IllegalStateException.class);
thrown.expectMessage("Metric 'unknown' cannot be used as an input metric as it's no a core metric and no plugin declare this metric");
thrown.expectMessage("Metric 'unknown' cannot be used as an input metric as it's not a core metric and no plugin declare this metric");

MeasureComputerProvider[] providers = new MeasureComputerProvider[] {
new NewMeasureComputerProvider(
array("unknown"),
array(NEW_METRIC_4),
mock(MeasureComputer.Implementation.class)),
};
MeasureComputer[] computers = new MeasureComputer[] {newMeasureComputer(array("unknown"), array(NEW_METRIC_4))};
ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), computers);
underTest.execute();
}

ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), providers);
@Test
public void fail_with_ISE_when_output_metric_is_not_define_by_plugin() throws Exception {
thrown.expect(IllegalStateException.class);
thrown.expectMessage("Metric 'unknown' cannot be used as an output metric as no plugin declare this metric");

MeasureComputer[] computers = new MeasureComputer[] {newMeasureComputer(array(NEW_METRIC_4), array("unknown"))};
ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), computers);
underTest.execute();
}

@Test
public void fail_with_ISE_when_output_metric_is_a_core_metric() throws Exception {
thrown.expect(IllegalStateException.class);
thrown.expectMessage("Metric 'ncloc' cannot be used as an output metric as it's a core metric");

MeasureComputer[] computers = new MeasureComputer[] {newMeasureComputer(array(NEW_METRIC_4), array(NCLOC_KEY))};
ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), computers);
underTest.execute();
}

@Test
public void not_fail_if_input_metrics_are_same_as_output_metrics() throws Exception {
MeasureComputer.Implementation implementation = mock(MeasureComputer.Implementation.class);
MeasureComputerProvider[] providers = new MeasureComputerProvider[] {
new NewMeasureComputerProvider(
array(NEW_METRIC_1),
array(NEW_METRIC_1),
implementation),
};
ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), providers);
MeasureComputer[] computers = new MeasureComputer[] {newMeasureComputer(array(NEW_METRIC_1), array(NEW_METRIC_1))};
ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), computers);
underTest.execute();

assertThat(holder.getMeasureComputers()).hasSize(1);
@@ -156,21 +160,6 @@ public class FeedMeasureComputersTest {
assertThat(holder.getMeasureComputers()).isEmpty();
}

@Test
public void support_no_plugin_metrics() throws Exception {
MeasureComputer.Implementation implementation = mock(MeasureComputer.Implementation.class);
MeasureComputerProvider[] providers = new MeasureComputerProvider[] {
new NewMeasureComputerProvider(
array(CoreMetrics.NCLOC_KEY),
array(CoreMetrics.COMMENT_LINES_KEY),
implementation),
};
ComputationStep underTest = new FeedMeasureComputers(holder, providers);
underTest.execute();

assertThat(holder.getMeasureComputers()).hasSize(1);
}

@Test
public void return_empty_list_when_no_metrics_neither_measure_computers() throws Exception {
ComputationStep underTest = new FeedMeasureComputers(holder);
@@ -179,26 +168,65 @@ public class FeedMeasureComputersTest {
assertThat(holder.getMeasureComputers()).isEmpty();
}

private static class NewMeasureComputerProvider implements MeasureComputerProvider {
@Test
public void fail_with_ISE_when_no_metrics_are_defined_by_plugin_but_measure_computer_use_a_new_metric() throws Exception {
thrown.expect(IllegalStateException.class);
thrown.expectMessage("Metric 'metric1' cannot be used as an output metric as no plugin declare this metric");

MeasureComputer[] computers = new MeasureComputer[] {newMeasureComputer(array(NCLOC_KEY), array(NEW_METRIC_1))};
ComputationStep underTest = new FeedMeasureComputers(holder, computers);
underTest.execute();
}

private final String[] inputMetrics;
private final String[] outputMetrics;
private final MeasureComputer.Implementation measureComputerImplementation;
@Test
public void fail_with_IAE_when_creating_measure_computer_definition_without_using_the_builder_and_with_invalid_output_metrics() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("At least one output metric must be defined");

MeasureComputer measureComputer = new MeasureComputer() {
@Override
public MeasureComputerDefinition define(MeasureComputerDefinitionContext defContext) {
// Create a instance of MeasureComputerDefinition without using the builder
return new MeasureComputer.MeasureComputerDefinition(){
@Override
public Set<String> getInputMetrics() {
return ImmutableSet.of(NCLOC_KEY);
}

@Override
public Set<String> getOutputMetrics() {
// Empty output metric is not allowed !
return Collections.emptySet();
}
};
}

@Override
public void compute(MeasureComputerContext context) {
// Nothing needs to be done as we're only testing metada
}
};

public NewMeasureComputerProvider(String[] inputMetrics, String[] outputMetrics, MeasureComputer.Implementation measureComputerImplementation) {
this.inputMetrics = inputMetrics;
this.outputMetrics = outputMetrics;
this.measureComputerImplementation = measureComputerImplementation;
}
MeasureComputer[] computers = new MeasureComputer[] {measureComputer};
ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), computers);
underTest.execute();
}

@Override
public void register(Context ctx) {
ctx.add(ctx.newMeasureComputerBuilder()
.setInputMetrics(inputMetrics)
.setOutputMetrics(outputMetrics)
.setImplementation(measureComputerImplementation)
.build());
}
private static MeasureComputer newMeasureComputer(final String[] inputMetrics, final String[] outputMetrics) {
return new MeasureComputer() {
@Override
public MeasureComputerDefinition define(MeasureComputerDefinitionContext defContext) {
return defContext.newDefinitionBuilder()
.setInputMetrics(inputMetrics)
.setOutputMetrics(outputMetrics)
.build();
}

@Override
public void compute(MeasureComputerContext context) {
// Nothing needs to be done as we're only testing metada
}
};
}

private static class TestMetrics implements Metrics {

+ 1
- 2
sonar-plugin-api/src/main/java/org/sonar/api/batch/Decorator.java View File

@@ -24,8 +24,7 @@ import org.sonar.api.resources.Resource;

/**
* @since 1.10
* @deprecated since 5.2 there's no more decorator on batch side
* TODO add link to new API
* @deprecated since 5.2 there's no more decorator on batch side. Use {@link org.sonar.api.ce.measure.MeasureComputer} instead
*/
@BatchSide
@ExtensionPoint

+ 5
- 0
sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/Component.java View File

@@ -22,6 +22,11 @@ package org.sonar.api.ce.measure;

import javax.annotation.CheckForNull;

/**
* Component that can be used in a {@link MeasureComputer}
*
* @since 5.2
*/
public interface Component {

enum Type {

+ 5
- 0
sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/Issue.java View File

@@ -24,6 +24,11 @@ import javax.annotation.CheckForNull;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.Duration;

/**
* Issue that can be used in a {@link MeasureComputer}
*
* @since 5.2
*/
public interface Issue {

String key();

+ 5
- 0
sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/Measure.java View File

@@ -20,6 +20,11 @@

package org.sonar.api.ce.measure;

/**
* Measure used in {@link MeasureComputer}
*
* @since 5.2
*/
public interface Measure {

/**

+ 159
- 97
sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/MeasureComputer.java View File

@@ -23,136 +23,198 @@ package org.sonar.api.ce.measure;
import java.util.List;
import java.util.Set;
import javax.annotation.CheckForNull;
import org.sonar.api.ExtensionPoint;
import org.sonar.api.ce.measure.MeasureComputer.MeasureComputerDefinition.Builder;
import org.sonar.api.server.ServerSide;

/**
* This class is used to define which metrics are required to compute some measures on some given metrics, and to define the implementation of the measures computation
* Define how to compute new measures on some metrics declared by {@link org.sonar.api.measures.Metrics}.
* <p/>
* This interface replaces the deprecated class org.sonar.api.batch.Decorator.
* <p/>
* <h3>How to use</h3>
* <pre>
* public class MyMeasureComputer implements MeasureComputer {
*
* {@literal @}Override
* public MeasureComputerDefinition define(MeasureComputerDefinitionContext defContext) {
* return defContext.newDefinitionBuilder()
*
* // Input metrics can be empty, for instance if only issues will be read
* .setInputMetrics("ncloc")
*
* // Output metrics must contains at least one metric
* .setOutputMetrics("my_new_metric")
*
* .build();
* }
*
* {@literal @}Override
* public void compute(MeasureComputerContext context) {
* int ncloc = context.getMeasure("ncloc");
* List&lt;Issue&gt; issues = context.getIssues();
* if (ncloc != null && !issues.isEmpty()) {
* double value = issues.size() / ncloc;
* context.addMeasure("my_new_metric", value);
* }
* }
* }
* </pre>
* <p/>
* <h3>How to test</h3>
* <pre>
* public class MyMeasureComputerTest {
*
* MyMeasureComputer underTest = new MyMeasureComputer();
*
* @Test
* public void test_definition() {
* TestMeasureComputerDefinitionContext defContext = new TestMeasureComputerDefinitionContext();
* MeasureComputerDefinition def = underTest.define(defContext);
* assertThat(def).isNotNull();
* assertThat(def.getInputMetrics()).containsOnly("ncloc");
* assertThat(def.getOutputMetrics()).containsOnly("my_new_metric");
* }
*
* @Test
* public void sum_ncloc_and_issues() {
* TestMeasureComputerContext context = new TestMeasureComputerContext(underTest);
* context.addMeasure("ncloc", 2);
* context.setIssues(Arrays.asList(new TestIssue.Builder().setKey("ABCD").build()));
* underTest.compute(context);
*
* assertThat(context.getMeasureValue("my_new_metric")).isEqualTo(0.5);
* }
* </pre>
*
* @since 5.2
*/
@ServerSide
@ExtensionPoint
public interface MeasureComputer {

/**
* Return the metric keys that can be read using {@link Implementation.Context}.
*
* Can never be empty as it's checked in the builder
* Use to define which metrics are required to compute some measures on some given metrics
*/
Set<String> getInputMetrics();
MeasureComputerDefinition define(MeasureComputerDefinitionContext defContext);

/**
* Return the metric keys that can be create using {@link Implementation.Context}.
*
* Can never ne empty as it's checked om the builder
* Context specific to the definition of the measure computer
*/
Set<String> getOutputMetrics();

Implementation getImplementation();

interface MeasureComputerBuilder {

/**
* Input metrics can be empty (for instance when only issues are needed)
* @throws NullPointerException if inputMetrics is null
* @throws NullPointerException if the metrics contains a {@code null}
* */
MeasureComputerBuilder setInputMetrics(String... inputMetrics);
interface MeasureComputerDefinitionContext {
Builder newDefinitionBuilder();
}

interface MeasureComputerDefinition {
/**
* @throws IllegalArgumentException if there's not at least one output metrics
* @throws NullPointerException if the metrics contains a {@code null}
* Return the metric keys that can be read using {@link MeasureComputerContext}.
* Can be empty for instance when the computer only need to access to issues.
*/
MeasureComputerBuilder setOutputMetrics(String... outMetrics);
Set<String> getInputMetrics();

/**
* @throws NullPointerException if there's no implementation
* Return the metric keys that can be create using {@link MeasureComputerContext}.
* Can never ne empty.
*/
MeasureComputerBuilder setImplementation(Implementation impl);
Set<String> getOutputMetrics();

/**
* @throws NullPointerException if inputMetrics is null
* @throws NullPointerException if inputs metrics contains a {@code null}
* @throws IllegalArgumentException if there's not at least one output metrics
* @throws NullPointerException if outputs metrics contains a {@code null}
* @throws NullPointerException if there's no implementation
*/
MeasureComputer build();
interface Builder {
/**
* Input metrics can be empty (for instance when only issues are needed)
* @throws NullPointerException if inputMetrics is null
* @throws NullPointerException if the metrics contains a {@code null}
* */
Builder setInputMetrics(String... inputMetrics);

/**
* @throws NullPointerException if outputMetrics is null
* @throws IllegalArgumentException if there's not at least one output metrics
* @throws NullPointerException if the metrics contains a {@code null}
*/
Builder setOutputMetrics(String... outputMetrics);

/**
* @throws NullPointerException if inputMetrics is null
* @throws NullPointerException if inputs metrics contains a {@code null}
* @throws NullPointerException if outputMetrics is null
* @throws IllegalArgumentException if there's not at least one output metrics
* @throws NullPointerException if outputs metrics contains a {@code null}
*/
MeasureComputerDefinition build();
}
}

/**
* This interface must be implemented to define how the measures are computed.
* This method will be called on each component of the projects.
*/
interface Implementation {
void compute(MeasureComputerContext context);

/**
* Context specific to the computation of the measure(s) of a given component
*/
interface MeasureComputerContext {
/**
* This method will be called on each component of the projects.
* Returns the current component.
*/
void compute(Context ctx);
Component getComponent();

/**
* Context specific to the computation of the measure(s) of a given component
* Returns settings of the current component.
*/
interface Context {

/**
* Returns the current component.
*/
Component getComponent();
Settings getSettings();

/**
* Returns settings of the current component.
*/
Settings getSettings();

/**
* Returns the measure from a given metric on the current component.
*
* @throws IllegalArgumentException if the metric is not listed in {@link MeasureComputer#getInputMetrics()}
*/
@CheckForNull
Measure getMeasure(String metric);

/**
* Returns measures from a given metric on children of the current component.
* It no measure is found for a child, this measure is ignored
*
* @throws IllegalArgumentException if the metric is not listed in {@link MeasureComputer#getInputMetrics()} or in {@link MeasureComputer#getOutputMetrics()}
*/
Iterable<Measure> getChildrenMeasures(String metric);
/**
* Returns the measure from a given metric on the current component.
*
* @throws IllegalArgumentException if the metric is not listed in {@link MeasureComputerDefinition#getInputMetrics()}
*/
@CheckForNull
Measure getMeasure(String metric);

/**
* Add a new measure of a given metric which measure type will be int
*
* @throws IllegalArgumentException if the metric is not listed in {@link MeasureComputer#getOutputMetrics()}
* @throws UnsupportedOperationException if a measure for the specified metric already exists for the current component
*/
void addMeasure(String metric, int value);
/**
* Returns measures from a given metric on children of the current component.
* It no measure is found for a child, this measure is ignored
*
* @throws IllegalArgumentException if the metric is not listed in {@link MeasureComputerDefinition#getInputMetrics()}
* or in {@link MeasureComputerDefinition#getOutputMetrics()}
*/
Iterable<Measure> getChildrenMeasures(String metric);

/**
* Add a new measure of a given metric which measure type will be double
*
* @throws IllegalArgumentException if the metric is not listed in {@link MeasureComputer#getOutputMetrics()}
* @throws UnsupportedOperationException if a measure for the specified metric already exists for the current component
*/
void addMeasure(String metric, double value);
/**
* Add a new measure of a given metric which measure type will be int
*
* @throws IllegalArgumentException if the metric is not listed in {@link MeasureComputerDefinition#getOutputMetrics()}
* @throws UnsupportedOperationException if a measure for the specified metric already exists for the current component
*/
void addMeasure(String metric, int value);

/**
* Add a new measure of a given metric which measure type will be long
*
* @throws IllegalArgumentException if the metric is not listed in {@link MeasureComputer#getOutputMetrics()}
* @throws UnsupportedOperationException if a measure for the specified metric already exists for the current component
*/
void addMeasure(String metric, long value);
/**
* Add a new measure of a given metric which measure type will be double
*
* @throws IllegalArgumentException if the metric is not listed in {@link MeasureComputerDefinition#getOutputMetrics()}
* @throws UnsupportedOperationException if a measure for the specified metric already exists for the current component
*/
void addMeasure(String metric, double value);

/**
* Add a new measure of a given metric which measure type will be string
*
* @throws IllegalArgumentException if the metric is not listed in {@link MeasureComputer#getOutputMetrics()}
* @throws UnsupportedOperationException if a measure for the specified metric already exists for the current component
*/
void addMeasure(String metric, String value);
/**
* Add a new measure of a given metric which measure type will be long
*
* @throws IllegalArgumentException if the metric is not listed in {@link MeasureComputerDefinition#getOutputMetrics()}
* @throws UnsupportedOperationException if a measure for the specified metric already exists for the current component
*/
void addMeasure(String metric, long value);

/**
* Return list of issues of current component.
*/
List<? extends Issue> getIssues();
/**
* Add a new measure of a given metric which measure type will be string
*
* @throws IllegalArgumentException if the metric is not listed in {@link MeasureComputerDefinition#getOutputMetrics()}
* @throws UnsupportedOperationException if a measure for the specified metric already exists for the current component
*/
void addMeasure(String metric, String value);

}
/**
* Return list of all issues (open, closed, etc.) of current component.
*/
List<? extends Issue> getIssues();
}
}

+ 0
- 53
sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/MeasureComputerProvider.java View File

@@ -1,53 +0,0 @@
/*
* 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.api.ce.measure;

import org.sonar.api.ExtensionPoint;
import org.sonar.api.server.ServerSide;

/**
* This extension point can be used to register {@link MeasureComputer}(s) that will be able to compute measures when a batch report is processed by the Compute Engine
*/
@ServerSide
@ExtensionPoint
public interface MeasureComputerProvider {

/**
* Use this method to register a new measure computer.
*/
void register(Context ctx);

interface Context {

/**
* Add a new computer to the context.
*
* @throws UnsupportedOperationException when trying to add a computer providing some measures on metrics already defined by another {@link MeasureComputer}
*/
Context add(MeasureComputer measureComputer);

/**
* Use this method to build a MeasureComputer to be used in the {@link #add(MeasureComputer)} method
*/
MeasureComputer.MeasureComputerBuilder newMeasureComputerBuilder();

}
}

+ 1
- 0
sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/RangeDistributionBuilder.java View File

@@ -37,6 +37,7 @@ import org.sonar.api.utils.KeyValueFormat;
* <p>An example of usage : you wish to record the percentage of lines of code that belong to method
* with pre-defined ranges of complexity.</p>
*
* @since 5.2
*/
public class RangeDistributionBuilder {


+ 3
- 1
sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/Settings.java View File

@@ -23,7 +23,9 @@ package org.sonar.api.ce.measure;
import javax.annotation.CheckForNull;

/**
* Settings of the current component.
* Settings of the current component used in {@link MeasureComputer}
*
* @since 5.2
*/
public interface Settings {


Loading…
Cancel
Save