]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6730 Simplify API
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 1 Sep 2015 08:58:54 +0000 (10:58 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 2 Sep 2015 12:28:19 +0000 (14:28 +0200)
28 files changed:
server/sonar-server/src/main/java/org/sonar/server/computation/issue/ComponentIssuesRepository.java
server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolder.java
server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolderImpl.java
server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersVisitor.java
server/sonar-server/src/main/java/org/sonar/server/computation/measure/MutableMeasureComputersHolder.java
server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerContextImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerDefinitionImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImpl.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContext.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContext.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerWrapper.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/FeedMeasureComputers.java
server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureComputersHolderImplTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureComputersVisitorTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerContextImplTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerDefinitionImplTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContextTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContextTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedMeasureComputersTest.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/Decorator.java
sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/Component.java
sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/Issue.java
sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/Measure.java
sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/MeasureComputer.java
sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/MeasureComputerProvider.java [deleted file]
sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/RangeDistributionBuilder.java
sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/Settings.java

index fd8900ce56a8153deb1773a969e9c2a5ac5f79d8..7691503f6117b4edf0add44439b99461ff933932 100644 (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.
index 61a1ac39f33c709b5328dd3ca225b3f6d2468e8d..1abd4233ebee5a6693d08d1dd00f9c95d0c0df4f 100644 (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();
 }
index de32f552072ed2fd6c2a492e475bd45a4261b7d3..dc58e3068608946b3ff3eb94028e78432d81b774 100644 (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();
index 8998020f1115cddf7cf7fa1304ec13e9da38ddf5..2424ae21c6f9e2a8535cbf6f8ae6254aafe22562 100644 (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);
     }
   }
 }
index 1d297ec0f991274ba21018431e030d4ef2333fb4..0e68d3818b4b9f908728db5a5f0e8e06971da501 100644 (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);
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerContextImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerContextImpl.java
new file mode 100644 (file)
index 0000000..434e18d
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * 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.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicates;
+import com.google.common.collect.FluentIterable;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+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.Settings;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.server.computation.component.SettingsRepository;
+import org.sonar.server.computation.issue.ComponentIssuesRepository;
+import org.sonar.server.computation.measure.MeasureRepository;
+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 MeasureComputerContextImpl implements MeasureComputerContext {
+
+  private final SettingsRepository settings;
+  private final MeasureRepository measureRepository;
+  private final MetricRepository metricRepository;
+
+  private final org.sonar.server.computation.component.Component internalComponent;
+  private final Component component;
+  private final List<DefaultIssue> componentIssues;
+
+  private MeasureComputerDefinition definition;
+  private Set<String> allowedMetrics;
+
+  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);
+  }
+
+  private Component newComponent(org.sonar.server.computation.component.Component component) {
+    return new ComponentImpl(
+      component.getKey(),
+      Component.Type.valueOf(component.getType().name()),
+      component.getType() == org.sonar.server.computation.component.Component.Type.FILE ?
+        new ComponentImpl.FileAttributesImpl(component.getFileAttributes().getLanguageKey(), component.getFileAttributes().isUnitTest()) :
+        null);
+  }
+
+  /**
+   * 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(definition.getInputMetrics());
+    allowedMetrics.addAll(definition.getOutputMetrics());
+    return allowedMetrics;
+  }
+
+  @Override
+  public Component getComponent() {
+    return component;
+  }
+
+  @Override
+  public Settings getSettings() {
+    return new Settings() {
+      @Override
+      @CheckForNull
+      public String getString(String key) {
+        return getComponentSettings().getString(key);
+      }
+
+      @Override
+      public String[] getStringArray(String key) {
+        return getComponentSettings().getStringArray(key);
+      }
+    };
+  }
+
+  private org.sonar.api.config.Settings getComponentSettings() {
+    return settings.getSettings(internalComponent);
+  }
+
+  @Override
+  @CheckForNull
+  public Measure getMeasure(String metric) {
+    validateInputMetric(metric);
+    Optional<org.sonar.server.computation.measure.Measure> measure = measureRepository.getRawMeasure(internalComponent, metricRepository.getByKey(metric));
+    if (measure.isPresent()) {
+      return new MeasureImpl(measure.get());
+    }
+    return null;
+  }
+
+  @Override
+  public Iterable<Measure> getChildrenMeasures(String metric) {
+    validateInputMetric(metric);
+    return FluentIterable.from(internalComponent.getChildren())
+      .transform(new ComponentToMeasure(metricRepository.getByKey(metric)))
+      .transform(ToMeasureAPI.INSTANCE)
+      .filter(Predicates.notNull());
+  }
+
+  @Override
+  public void addMeasure(String metricKey, int value) {
+    Metric metric = metricRepository.getByKey(metricKey);
+    validateAddMeasure(metric);
+    measureRepository.add(internalComponent, metric, newMeasureBuilder().create(value));
+  }
+
+  @Override
+  public void addMeasure(String metricKey, double value) {
+    Metric metric = metricRepository.getByKey(metricKey);
+    validateAddMeasure(metric);
+    measureRepository.add(internalComponent, metric, newMeasureBuilder().create(value));
+  }
+
+  @Override
+  public void addMeasure(String metricKey, long value) {
+    Metric metric = metricRepository.getByKey(metricKey);
+    validateAddMeasure(metric);
+    measureRepository.add(internalComponent, metric, newMeasureBuilder().create(value));
+  }
+
+  @Override
+  public void addMeasure(String metricKey, String value) {
+    Metric metric = metricRepository.getByKey(metricKey);
+    validateAddMeasure(metric);
+    measureRepository.add(internalComponent, metric, newMeasureBuilder().create(value));
+  }
+
+  private void validateInputMetric(String metric) {
+    checkArgument(allowedMetrics.contains(metric), "Only metrics in %s can be used to load measures", definition.getInputMetrics());
+  }
+
+  private void validateAddMeasure(Metric metric) {
+    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()));
+    }
+  }
+
+  @Override
+  public List<? extends Issue> getIssues() {
+    return componentIssues;
+  }
+
+  private class ComponentToMeasure implements Function<org.sonar.server.computation.component.Component, Optional<org.sonar.server.computation.measure.Measure>> {
+
+    private final Metric metric;
+
+    public ComponentToMeasure(Metric metric) {
+      this.metric = metric;
+    }
+
+    @Override
+    public Optional<org.sonar.server.computation.measure.Measure> apply(@Nonnull org.sonar.server.computation.component.Component input) {
+      return measureRepository.getRawMeasure(input, metric);
+    }
+  }
+
+  private enum ToMeasureAPI implements Function<Optional<org.sonar.server.computation.measure.Measure>, Measure> {
+    INSTANCE;
+
+    @Nullable
+    @Override
+    public Measure apply(@Nonnull Optional<org.sonar.server.computation.measure.Measure> input) {
+      return input.isPresent() ? new MeasureImpl(input.get()) : null;
+    }
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerDefinitionImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerDefinitionImpl.java
new file mode 100644 (file)
index 0000000..e4532c4
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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.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 MeasureComputerDefinitionImpl implements MeasureComputer.MeasureComputerDefinition {
+
+  private final Set<String> inputMetricKeys;
+  private final Set<String> outputMetrics;
+
+  private MeasureComputerDefinitionImpl(BuilderImpl builder) {
+    this.inputMetricKeys = ImmutableSet.copyOf(builder.inputMetricKeys);
+    this.outputMetrics = ImmutableSet.copyOf(builder.outputMetrics);
+  }
+
+  @Override
+  public Set<String> getInputMetrics() {
+    return inputMetricKeys;
+  }
+
+  @Override
+  public Set<String> getOutputMetrics() {
+    return outputMetrics;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    MeasureComputerDefinitionImpl that = (MeasureComputerDefinitionImpl) o;
+
+    if (!inputMetricKeys.equals(that.inputMetricKeys)) {
+      return false;
+    }
+    return outputMetrics.equals(that.outputMetrics);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = inputMetricKeys.hashCode();
+    result = 31 * result + outputMetrics.hashCode();
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "MeasureComputerDefinitionImpl{" +
+      "inputMetricKeys=" + inputMetricKeys +
+      ", outputMetrics=" + outputMetrics +
+      '}';
+  }
+
+  public static class BuilderImpl implements Builder {
+
+    private String[] inputMetricKeys = new String[] {};
+    @CheckForNull
+    private String[] outputMetrics;
+
+    @Override
+    public Builder setInputMetrics(String... inputMetrics) {
+      this.inputMetricKeys = validateInputMetricKeys(inputMetrics);
+      return this;
+    }
+
+    @Override
+    public Builder setOutputMetrics(String... outputMetrics) {
+      this.outputMetrics = validateOutputMetricKeys(outputMetrics);
+      return this;
+    }
+
+    @Override
+    public MeasureComputer.MeasureComputerDefinition build() {
+      validateInputMetricKeys(this.inputMetricKeys);
+      validateOutputMetricKeys(this.outputMetrics);
+      return new MeasureComputerDefinitionImpl(this);
+    }
+
+    private static String[] validateInputMetricKeys(@Nullable String[] inputMetrics) {
+      requireNonNull(inputMetrics, "Input metrics cannot be null");
+      checkNotNull(inputMetrics);
+      return inputMetrics;
+    }
+
+    private static String[] validateOutputMetricKeys(@Nullable String[] outputMetrics) {
+      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 void checkNotNull(String[] metrics) {
+    for (String metric : metrics) {
+      requireNonNull(metric, "Null metric is not allowed");
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImpl.java
deleted file mode 100644 (file)
index 1712950..0000000
+++ /dev/null
@@ -1,146 +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.Set;
-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 {
-
-  private final Set<String> inputMetricKeys;
-  private final Set<String> outputMetrics;
-  private final Implementation implementation;
-
-  public MeasureComputerImpl(MeasureComputerBuilderImpl builder) {
-    this.inputMetricKeys = ImmutableSet.copyOf(builder.inputMetricKeys);
-    this.outputMetrics = ImmutableSet.copyOf(builder.outputMetrics);
-    this.implementation = builder.measureComputerImplementation;
-  }
-
-  @Override
-  public Set<String> getInputMetrics() {
-    return inputMetricKeys;
-  }
-
-  @Override
-  public Set<String> getOutputMetrics() {
-    return outputMetrics;
-  }
-
-  @Override
-  public Implementation getImplementation() {
-    return implementation;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-
-    MeasureComputerImpl that = (MeasureComputerImpl) o;
-
-    if (!inputMetricKeys.equals(that.inputMetricKeys)) {
-      return false;
-    }
-    return outputMetrics.equals(that.outputMetrics);
-  }
-
-  @Override
-  public int hashCode() {
-    int result = inputMetricKeys.hashCode();
-    result = 31 * result + outputMetrics.hashCode();
-    return result;
-  }
-
-  @Override
-  public String toString() {
-    return "MeasureComputerImpl{" +
-      "inputMetricKeys=" + inputMetricKeys +
-      ", outputMetrics=" + outputMetrics +
-      ", implementation=" + implementation +
-      '}';
-  }
-
-  public static class MeasureComputerBuilderImpl implements MeasureComputerBuilder {
-
-    private String[] inputMetricKeys = new String[] {};
-    private String[] outputMetrics;
-    private Implementation measureComputerImplementation;
-
-    @Override
-    public MeasureComputerBuilder setInputMetrics(String... inputMetrics) {
-      this.inputMetricKeys = validateInputMetricKeys(inputMetrics);
-      return this;
-    }
-
-    @Override
-    public MeasureComputerBuilder 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() {
-      validateInputMetricKeys(this.inputMetricKeys);
-      validateOutputMetricKeys(this.outputMetrics);
-      validateImplementation(this.measureComputerImplementation);
-      return new MeasureComputerImpl(this);
-    }
-
-    private static String[] validateInputMetricKeys(@Nullable String[] inputMetrics) {
-      requireNonNull(inputMetrics, "Input metrics cannot be null");
-      checkNotNull(inputMetrics);
-      return inputMetrics;
-    }
-
-    private static String[] validateOutputMetricKeys(@Nullable String[] outputMetrics) {
-      checkArgument(outputMetrics != null && 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){
-    for (String metric : metrics) {
-      requireNonNull(metric, "Null metric is not allowed");
-    }
-  }
-
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContext.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContext.java
deleted file mode 100644 (file)
index 237846e..0000000
+++ /dev/null
@@ -1,203 +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.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.base.Predicates;
-import com.google.common.collect.FluentIterable;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
-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;
-import org.sonar.server.computation.issue.ComponentIssuesRepository;
-import org.sonar.server.computation.measure.MeasureRepository;
-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.server.computation.measure.Measure.newMeasureBuilder;
-
-public class MeasureComputerImplementationContext implements MeasureComputer.Implementation.Context {
-
-  private final MeasureComputer measureComputer;
-  private final SettingsRepository settings;
-  private final MeasureRepository measureRepository;
-  private final MetricRepository metricRepository;
-
-  private final org.sonar.server.computation.component.Component internalComponent;
-  private final Component component;
-  private final List<DefaultIssue> componentIssues;
-
-  private final 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;
-    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) {
-    return new ComponentImpl(
-      component.getKey(),
-      Component.Type.valueOf(component.getType().name()),
-      component.getType() == org.sonar.server.computation.component.Component.Type.FILE ?
-        new ComponentImpl.FileAttributesImpl(component.getFileAttributes().getLanguageKey(), component.getFileAttributes().isUnitTest()) :
-        null);
-  }
-
-  private static Set<String> allowedMetric(MeasureComputer measureComputer) {
-    Set<String> allowedMetrics = new HashSet<>();
-    allowedMetrics.addAll(measureComputer.getInputMetrics());
-    allowedMetrics.addAll(measureComputer.getOutputMetrics());
-    return allowedMetrics;
-  }
-
-  @Override
-  public Component getComponent() {
-    return component;
-  }
-
-  @Override
-  public Settings getSettings() {
-    return new Settings() {
-      @Override
-      @CheckForNull
-      public String getString(String key) {
-        return getComponentSettings().getString(key);
-      }
-
-      @Override
-      public String[] getStringArray(String key) {
-        return getComponentSettings().getStringArray(key);
-      }
-    };
-  }
-
-  private org.sonar.api.config.Settings getComponentSettings() {
-    return settings.getSettings(internalComponent);
-  }
-
-  @Override
-  @CheckForNull
-  public Measure getMeasure(String metric) {
-    validateInputMetric(metric);
-    Optional<org.sonar.server.computation.measure.Measure> measure = measureRepository.getRawMeasure(internalComponent, metricRepository.getByKey(metric));
-    if (measure.isPresent()) {
-      return new MeasureImpl(measure.get());
-    }
-    return null;
-  }
-
-  @Override
-  public Iterable<Measure> getChildrenMeasures(String metric) {
-    validateInputMetric(metric);
-    return FluentIterable.from(internalComponent.getChildren())
-      .transform(new ComponentToMeasure(metricRepository.getByKey(metric)))
-      .transform(ToMeasureAPI.INSTANCE)
-      .filter(Predicates.notNull());
-  }
-
-  @Override
-  public void addMeasure(String metricKey, int value) {
-    Metric metric = metricRepository.getByKey(metricKey);
-    validateAddMeasure(metric);
-    measureRepository.add(internalComponent, metric, newMeasureBuilder().create(value));
-  }
-
-  @Override
-  public void addMeasure(String metricKey, double value) {
-    Metric metric = metricRepository.getByKey(metricKey);
-    validateAddMeasure(metric);
-    measureRepository.add(internalComponent, metric, newMeasureBuilder().create(value));
-  }
-
-  @Override
-  public void addMeasure(String metricKey, long value) {
-    Metric metric = metricRepository.getByKey(metricKey);
-    validateAddMeasure(metric);
-    measureRepository.add(internalComponent, metric, newMeasureBuilder().create(value));
-  }
-
-  @Override
-  public void addMeasure(String metricKey, String value) {
-    Metric metric = metricRepository.getByKey(metricKey);
-    validateAddMeasure(metric);
-    measureRepository.add(internalComponent, metric, newMeasureBuilder().create(value));
-  }
-
-  private void validateInputMetric(String metric) {
-    checkArgument(allowedMetrics.contains(metric), "Only metrics in %s can be used to load measures", measureComputer.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());
-    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()));
-    }
-  }
-
-  @Override
-  public List<? extends Issue> getIssues() {
-    return componentIssues;
-  }
-
-  private class ComponentToMeasure implements Function<org.sonar.server.computation.component.Component, Optional<org.sonar.server.computation.measure.Measure>> {
-
-    private final Metric metric;
-
-    public ComponentToMeasure(Metric metric) {
-      this.metric = metric;
-    }
-
-    @Override
-    public Optional<org.sonar.server.computation.measure.Measure> apply(@Nonnull org.sonar.server.computation.component.Component input) {
-      return measureRepository.getRawMeasure(input, metric);
-    }
-  }
-
-  private enum ToMeasureAPI implements Function<Optional<org.sonar.server.computation.measure.Measure>, Measure> {
-    INSTANCE;
-
-    @Nullable
-    @Override
-    public Measure apply(@Nonnull Optional<org.sonar.server.computation.measure.Measure> input) {
-      return input.isPresent() ? new MeasureImpl(input.get()) : null;
-    }
-  }
-
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContext.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContext.java
deleted file mode 100644 (file)
index 27be1a2..0000000
+++ /dev/null
@@ -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();
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerWrapper.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerWrapper.java
new file mode 100644 (file)
index 0000000..6da2da7
--- /dev/null
@@ -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();
+  }
+}
index 48dc14cbf1e4916c43340fbce2eb10a26cbeda11..eff2e6626ab3f234f0c04ce75489efc78d4886bb 100644 (file)
 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";
index a42ce3ed72caa3e1ff9831cd22fb4179d2d69c89..83529efa4b03818aaf7d7455a99fabe13f60c6d5 100644 (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));
   }
index 9aa1cac9d2e23f55e0bbfef03714bfd70b5b035a..da699436f8d10929076a48b1aca5c80f6d2a15be 100644 (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();
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerContextImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerContextImplTest.java
new file mode 100644 (file)
index 0000000..74c2cd6
--- /dev/null
@@ -0,0 +1,286 @@
+/*
+ * 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.base.Optional;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.ce.measure.Component;
+import org.sonar.api.ce.measure.MeasureComputer;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.Duration;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.server.computation.batch.TreeRootHolderRule;
+import org.sonar.server.computation.component.SettingsRepository;
+import org.sonar.server.computation.issue.ComponentIssuesRepositoryRule;
+import org.sonar.server.computation.measure.Measure;
+import org.sonar.server.computation.measure.MeasureRepositoryRule;
+import org.sonar.server.computation.metric.Metric;
+import org.sonar.server.computation.metric.MetricImpl;
+import org.sonar.server.computation.metric.MetricRepositoryRule;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.guava.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.measures.CoreMetrics.COMMENT_LINES_KEY;
+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 MeasureComputerContextImplTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  private static final String INT_METRIC_KEY = "int_metric_key";
+  private static final String DOUBLE_METRIC_KEY = "double_metric_key";
+  private static final String LONG_METRIC_KEY = "long_metric_key";
+  private static final String STRING_METRIC_KEY = "string_metric_key";
+
+  private static final int PROJECT_REF = 1;
+  private static final int FILE_1_REF = 12341;
+  private static final String FILE_1_KEY = "fileKey";
+  private static final int FILE_2_REF = 12342;
+
+  private static final org.sonar.server.computation.component.Component FILE_1 = builder(org.sonar.server.computation.component.Component.Type.FILE, FILE_1_REF)
+    .setKey(FILE_1_KEY)
+    .build();
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule()
+    .setRoot(builder(org.sonar.server.computation.component.Component.Type.PROJECT, PROJECT_REF).setKey("project")
+      .addChildren(
+        FILE_1,
+        builder(org.sonar.server.computation.component.Component.Type.FILE, FILE_2_REF).setKey("fileKey2").build()
+      ).build());
+
+  @Rule
+  public MetricRepositoryRule metricRepository = new MetricRepositoryRule()
+    .add(1, CoreMetrics.NCLOC)
+    .add(new MetricImpl(2, INT_METRIC_KEY, "int metric", Metric.MetricType.INT))
+    .add(new MetricImpl(3, DOUBLE_METRIC_KEY, "double metric", Metric.MetricType.FLOAT))
+    .add(new MetricImpl(4, LONG_METRIC_KEY, "long metric", Metric.MetricType.MILLISEC))
+    .add(new MetricImpl(5, STRING_METRIC_KEY, "string metric", Metric.MetricType.STRING));
+
+  @Rule
+  public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
+
+  @Rule
+  public ComponentIssuesRepositoryRule componentIssuesRepository = new ComponentIssuesRepositoryRule(treeRootHolder);
+
+  SettingsRepository settingsRepository = mock(SettingsRepository.class);
+
+  @Test
+  public void get_component() throws Exception {
+    MeasureComputerContextImpl underTest = newContext(FILE_1_REF);
+    assertThat(underTest.getComponent().getType()).isEqualTo(Component.Type.FILE);
+  }
+
+  @Test
+  public void get_string_settings() throws Exception {
+    org.sonar.api.config.Settings serverSettings = new org.sonar.api.config.Settings();
+    serverSettings.setProperty("prop", "value");
+    when(settingsRepository.getSettings(FILE_1)).thenReturn(serverSettings);
+
+    MeasureComputerContextImpl underTest = newContext(FILE_1_REF);
+    assertThat(underTest.getSettings().getString("prop")).isEqualTo("value");
+    assertThat(underTest.getSettings().getString("unknown")).isNull();
+  }
+
+  @Test
+  public void get_string_array_settings() throws Exception {
+    org.sonar.api.config.Settings serverSettings = new org.sonar.api.config.Settings();
+    serverSettings.setProperty("prop", "1,3.4,8,50");
+    when(settingsRepository.getSettings(FILE_1)).thenReturn(serverSettings);
+
+    MeasureComputerContextImpl underTest = newContext(FILE_1_REF);
+    assertThat(underTest.getSettings().getStringArray("prop")).containsExactly("1", "3.4", "8", "50");
+    assertThat(underTest.getSettings().getStringArray("unknown")).isEmpty();
+  }
+
+  @Test
+  public void get_measure() throws Exception {
+    measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(10));
+
+    MeasureComputerContextImpl underTest = newContext(FILE_1_REF, NCLOC_KEY, COMMENT_LINES_KEY);
+    assertThat(underTest.getMeasure(NCLOC_KEY).getIntValue()).isEqualTo(10);
+  }
+
+  @Test
+  public void fail_with_IAE_when_get_measure_is_called_on_metric_not_in_input_list() throws Exception {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Only metrics in [another metric] can be used to load measures");
+
+    MeasureComputerContextImpl underTest = newContext(PROJECT_REF, "another metric", "debt");
+    underTest.getMeasure(NCLOC_KEY);
+  }
+
+  @Test
+  public void get_children_measures() throws Exception {
+    measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(10));
+    measureRepository.addRawMeasure(FILE_2_REF, NCLOC_KEY, newMeasureBuilder().create(12));
+
+    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);
+  }
+
+  @Test
+  public void get_children_measures_when_one_child_has_no_value() throws Exception {
+    measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(10));
+    // No data on file 2
+
+    MeasureComputerContextImpl underTest = newContext(PROJECT_REF, NCLOC_KEY, COMMENT_LINES_KEY);
+    assertThat(underTest.getChildrenMeasures(NCLOC_KEY)).extracting("intValue").containsOnly(10);
+  }
+
+  @Test
+  public void not_fail_to_get_children_measures_on_output_metric() throws Exception {
+    measureRepository.addRawMeasure(FILE_1_REF, INT_METRIC_KEY, newMeasureBuilder().create(10));
+
+    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);
+  }
+
+  @Test
+  public void fail_with_IAE_when_get_children_measures_is_called_on_metric_not_in_input_list() throws Exception {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Only metrics in [another metric] can be used to load measures");
+
+    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 {
+    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);
+    assertThat(measure).isPresent();
+    assertThat(measure.get().getIntValue()).isEqualTo(10);
+  }
+
+  @Test
+  public void add_double_measure_create_measure_of_type_double_with_right_value() throws Exception {
+    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);
+    assertThat(measure).isPresent();
+    assertThat(measure.get().getDoubleValue()).isEqualTo(10d);
+  }
+
+  @Test
+  public void add_long_measure_create_measure_of_type_long_with_right_value() throws Exception {
+    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);
+    assertThat(measure).isPresent();
+    assertThat(measure.get().getLongValue()).isEqualTo(10L);
+  }
+
+  @Test
+  public void add_string_measure_create_measure_of_type_string_with_right_value() throws Exception {
+    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);
+    assertThat(measure).isPresent();
+    assertThat(measure.get().getStringValue()).isEqualTo("data");
+  }
+
+  @Test
+  public void fail_with_IAE_when_add_measure_is_called_on_metric_not_in_output_list() throws Exception {
+    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.");
+
+    MeasureComputerContextImpl underTest = newContext(PROJECT_REF, NCLOC_KEY, INT_METRIC_KEY);
+    underTest.addMeasure(DOUBLE_METRIC_KEY, 10);
+  }
+
+  @Test
+  public void fail_with_unsupported_operation_when_adding_measure_that_already_exists() throws Exception {
+    thrown.expect(UnsupportedOperationException.class);
+    thrown.expectMessage("A measure on metric 'int_metric_key' already exists on component 'fileKey'");
+
+    measureRepository.addRawMeasure(FILE_1_REF, INT_METRIC_KEY, newMeasureBuilder().create(20));
+
+    MeasureComputerContextImpl underTest = newContext(FILE_1_REF, NCLOC_KEY, INT_METRIC_KEY);
+    underTest.addMeasure(INT_METRIC_KEY, 10);
+  }
+
+  @Test
+  public void get_issues() throws Exception {
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("KEY")
+      .setRuleKey(RuleKey.of("xoo", "S01"))
+      .setSeverity("MAJOR")
+      .setStatus("CLOSED")
+      .setResolution("FIXED")
+      .setDebt(Duration.create(10l));
+
+    MeasureComputerContextImpl underTest = newContext(PROJECT_REF, Arrays.asList(issue));
+
+    assertThat(underTest.getIssues()).hasSize(1);
+    org.sonar.api.ce.measure.Issue result = underTest.getIssues().get(0);
+    assertThat(result.key()).isEqualTo("KEY");
+    assertThat(result.ruleKey()).isEqualTo(RuleKey.of("xoo", "S01"));
+    assertThat(result.severity()).isEqualTo("MAJOR");
+    assertThat(result.status()).isEqualTo("CLOSED");
+    assertThat(result.resolution()).isEqualTo("FIXED");
+    assertThat(result.debt()).isEqualTo(Duration.create(10l));
+  }
+
+  private MeasureComputerContextImpl newContext(int componentRef) {
+    return newContext(componentRef, NCLOC_KEY, COMMENT_LINES_KEY, Collections.<DefaultIssue>emptyList());
+  }
+
+  private MeasureComputerContextImpl newContext(int componentRef, List<DefaultIssue> issues) {
+    return newContext(componentRef, NCLOC_KEY, COMMENT_LINES_KEY, issues);
+  }
+
+  private MeasureComputerContextImpl newContext(int componentRef, String inputMetric, String outputMetric) {
+    return newContext(componentRef, inputMetric, outputMetric, Collections.<DefaultIssue>emptyList());
+  }
+
+  private MeasureComputerContextImpl newContext(int componentRef, String inputMetric, String outputMetric, List<DefaultIssue> issues) {
+    componentIssuesRepository.setIssues(componentRef, issues);
+
+    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;
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerDefinitionImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerDefinitionImplTest.java
new file mode 100644 (file)
index 0000000..5e9b6c0
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * 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 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 MeasureComputerDefinitionImplTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void build_measure_computer_definition() throws Exception {
+    String inputMetric = "ncloc";
+    String outputMetric = "comment_density";
+    MeasureComputer.MeasureComputerDefinition measureComputer = new MeasureComputerDefinitionImpl.BuilderImpl()
+      .setInputMetrics(inputMetric)
+      .setOutputMetrics(outputMetric)
+      .build();
+
+    assertThat(measureComputer.getInputMetrics()).containsOnly(inputMetric);
+    assertThat(measureComputer.getOutputMetrics()).containsOnly(outputMetric);
+  }
+
+  @Test
+  public void build_measure_computer_with_multiple_metrics() throws Exception {
+    String[] inputMetrics = {"ncloc", "comment"};
+    String[] outputMetrics = {"comment_density_1", "comment_density_2"};
+    MeasureComputer.MeasureComputerDefinition measureComputer = new MeasureComputerDefinitionImpl.BuilderImpl()
+      .setInputMetrics(inputMetrics)
+      .setOutputMetrics(outputMetrics)
+      .build();
+
+    assertThat(measureComputer.getInputMetrics()).containsOnly(inputMetrics);
+    assertThat(measureComputer.getOutputMetrics()).containsOnly(outputMetrics);
+  }
+
+  @Test
+  public void input_metrics_can_be_empty() throws Exception {
+    MeasureComputer.MeasureComputerDefinition measureComputer = new MeasureComputerDefinitionImpl.BuilderImpl()
+      .setInputMetrics()
+      .setOutputMetrics("comment_density_1", "comment_density_2")
+      .build();
+
+    assertThat(measureComputer.getInputMetrics()).isEmpty();
+  }
+
+  @Test
+  public void input_metrics_is_empty_when_not_set() throws Exception {
+    MeasureComputer.MeasureComputerDefinition measureComputer = new MeasureComputerDefinitionImpl.BuilderImpl()
+      .setOutputMetrics("comment_density_1", "comment_density_2")
+      .build();
+
+    assertThat(measureComputer.getInputMetrics()).isEmpty();
+  }
+
+  @Test
+  public void fail_with_NPE_when_null_input_metrics() throws Exception {
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("Input metrics cannot be null");
+
+    new MeasureComputerDefinitionImpl.BuilderImpl()
+      .setInputMetrics(null)
+      .setOutputMetrics("comment_density_1", "comment_density_2");
+  }
+
+  @Test
+  public void fail_with_NPE_when_one_input_metric_is_null() throws Exception {
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("Null metric is not allowed");
+
+    new MeasureComputerDefinitionImpl.BuilderImpl()
+      .setInputMetrics("ncloc", null)
+      .setOutputMetrics("comment_density_1", "comment_density_2");
+  }
+
+  @Test
+  public void fail_with_NPE_when_no_output_metrics() throws Exception {
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("Output metrics cannot be null");
+
+    new MeasureComputerDefinitionImpl.BuilderImpl()
+      .setInputMetrics("ncloc", "comment")
+      .build();
+  }
+
+  @Test
+  public void fail_with_NPE_when_null_output_metrics() throws Exception {
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("Output metrics cannot be null");
+
+    new MeasureComputerDefinitionImpl.BuilderImpl()
+      .setInputMetrics("ncloc", "comment")
+      .setOutputMetrics(null);
+  }
+
+  @Test
+  public void fail_with_NPE_when_one_output_metric_is_null() throws Exception {
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("Null metric is not allowed");
+
+    new MeasureComputerDefinitionImpl.BuilderImpl()
+      .setInputMetrics("ncloc", "comment")
+      .setOutputMetrics("comment_density_1", null);
+  }
+
+  @Test
+  public void fail_with_IAE_with_empty_output_metrics() throws Exception {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("At least one output metric must be defined");
+
+    new MeasureComputerDefinitionImpl.BuilderImpl()
+      .setInputMetrics("ncloc", "comment")
+      .setOutputMetrics();
+  }
+
+  @Test
+  public void test_equals_and_hashcode() throws Exception {
+    MeasureComputer.MeasureComputerDefinition computer = new MeasureComputerDefinitionImpl.BuilderImpl()
+      .setInputMetrics("ncloc", "comment")
+      .setOutputMetrics("comment_density_1", "comment_density_2")
+      .build();
+
+    MeasureComputer.MeasureComputerDefinition sameComputer = new MeasureComputerDefinitionImpl.BuilderImpl()
+      .setInputMetrics("comment", "ncloc")
+      .setOutputMetrics("comment_density_2", "comment_density_1")
+      .build();
+
+    MeasureComputer.MeasureComputerDefinition anotherComputer = new MeasureComputerDefinitionImpl.BuilderImpl()
+      .setInputMetrics("comment")
+      .setOutputMetrics("debt")
+      .build();
+
+    assertThat(computer).isEqualTo(computer);
+    assertThat(computer).isEqualTo(sameComputer);
+    assertThat(computer).isNotEqualTo(anotherComputer);
+    assertThat(computer).isNotEqualTo(null);
+
+    assertThat(computer.hashCode()).isEqualTo(computer.hashCode());
+    assertThat(computer.hashCode()).isEqualTo(sameComputer.hashCode());
+    assertThat(computer.hashCode()).isNotEqualTo(anotherComputer.hashCode());
+  }
+
+  @Test
+  public void test_to_string() throws Exception {
+    assertThat(new MeasureComputerDefinitionImpl.BuilderImpl()
+      .setInputMetrics("ncloc", "comment")
+      .setOutputMetrics("comment_density_1", "comment_density_2")
+      .build().toString())
+      .isEqualTo("MeasureComputerDefinitionImpl{inputMetricKeys=[ncloc, comment], outputMetrics=[comment_density_1, comment_density_2]}");
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplTest.java
deleted file mode 100644 (file)
index 98eb6b0..0000000
+++ /dev/null
@@ -1,225 +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 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 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";
-    }
-  };
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  @Test
-  public void build_measure_computer() throws Exception {
-    String inputMetric = "ncloc";
-    String outputMetric = "comment_density";
-    MeasureComputer measureComputer = new MeasureComputerImpl.MeasureComputerBuilderImpl()
-      .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()
-      .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()
-      .setInputMetrics()
-      .setOutputMetrics("comment_density_1", "comment_density_2")
-      .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION)
-      .build();
-
-    assertThat(measureComputer.getInputMetrics()).isEmpty();
-  }
-
-  @Test
-  public void input_metrics_is_empty_when_not_set() throws Exception {
-    MeasureComputer measureComputer = new MeasureComputerImpl.MeasureComputerBuilderImpl()
-      .setOutputMetrics("comment_density_1", "comment_density_2")
-      .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION)
-      .build();
-
-    assertThat(measureComputer.getInputMetrics()).isEmpty();
-  }
-
-  @Test
-  public void fail_with_NPE_when_null_input_metrics() throws Exception {
-    thrown.expect(NullPointerException.class);
-    thrown.expectMessage("Input metrics cannot be null");
-
-    new MeasureComputerImpl.MeasureComputerBuilderImpl()
-      .setInputMetrics(null)
-      .setOutputMetrics("comment_density_1", "comment_density_2")
-      .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION);
-  }
-
-  @Test
-  public void fail_with_NPE_when_one_input_metric_is_null() throws Exception {
-    thrown.expect(NullPointerException.class);
-    thrown.expectMessage("Null metric is not allowed");
-
-    new MeasureComputerImpl.MeasureComputerBuilderImpl()
-      .setInputMetrics("ncloc", null)
-      .setOutputMetrics("comment_density_1", "comment_density_2")
-      .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION);
-  }
-
-  @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");
-
-    new MeasureComputerImpl.MeasureComputerBuilderImpl()
-      .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");
-
-    new MeasureComputerImpl.MeasureComputerBuilderImpl()
-      .setInputMetrics("ncloc", "comment")
-      .setOutputMetrics(null)
-      .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION);
-  }
-
-  @Test
-  public void fail_with_NPE_when_one_output_metric_is_null() throws Exception {
-    thrown.expect(NullPointerException.class);
-    thrown.expectMessage("Null metric is not allowed");
-
-    new MeasureComputerImpl.MeasureComputerBuilderImpl()
-      .setInputMetrics("ncloc", "comment")
-      .setOutputMetrics("comment_density_1", null)
-      .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION);
-  }
-
-  @Test
-  public void fail_with_IAE_with_empty_output_metrics() throws Exception {
-    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()
-      .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);
-  }
-
-  @Test
-  public void test_equals_and_hashcode() throws Exception {
-    MeasureComputer computer = new MeasureComputerImpl.MeasureComputerBuilderImpl()
-      .setInputMetrics("ncloc", "comment")
-      .setOutputMetrics("comment_density_1", "comment_density_2")
-      .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION)
-      .build();
-
-    MeasureComputer sameComputer = new MeasureComputerImpl.MeasureComputerBuilderImpl()
-      .setInputMetrics("comment", "ncloc")
-      .setOutputMetrics("comment_density_2", "comment_density_1")
-      .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION)
-      .build();
-
-    MeasureComputer anotherComputer = new MeasureComputerImpl.MeasureComputerBuilderImpl()
-      .setInputMetrics("comment")
-      .setOutputMetrics("debt")
-      .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION)
-      .build();
-
-    assertThat(computer).isEqualTo(computer);
-    assertThat(computer).isEqualTo(sameComputer);
-    assertThat(computer).isNotEqualTo(anotherComputer);
-    assertThat(computer).isNotEqualTo(null);
-
-    assertThat(computer.hashCode()).isEqualTo(computer.hashCode());
-    assertThat(computer.hashCode()).isEqualTo(sameComputer.hashCode());
-    assertThat(computer.hashCode()).isNotEqualTo(anotherComputer.hashCode());
-  }
-
-  @Test
-  public void test_to_string() throws Exception {
-    assertThat(new MeasureComputerImpl.MeasureComputerBuilderImpl()
-      .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}");
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContextTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContextTest.java
deleted file mode 100644 (file)
index cf4d8b0..0000000
+++ /dev/null
@@ -1,296 +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.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;
-import org.sonar.api.ce.measure.Component;
-import org.sonar.api.ce.measure.MeasureComputer;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.utils.Duration;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.server.computation.batch.TreeRootHolderRule;
-import org.sonar.server.computation.component.SettingsRepository;
-import org.sonar.server.computation.issue.ComponentIssuesRepositoryRule;
-import org.sonar.server.computation.measure.Measure;
-import org.sonar.server.computation.measure.MeasureRepositoryRule;
-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;
-import static org.mockito.Mockito.when;
-import static org.sonar.api.measures.CoreMetrics.COMMENT_LINES_KEY;
-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 {
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  private static final String INT_METRIC_KEY = "int_metric_key";
-  private static final String DOUBLE_METRIC_KEY = "double_metric_key";
-  private static final String LONG_METRIC_KEY = "long_metric_key";
-  private static final String STRING_METRIC_KEY = "string_metric_key";
-
-  private static final int PROJECT_REF = 1;
-  private static final int FILE_1_REF = 12341;
-  private static final String FILE_1_KEY = "fileKey";
-  private static final int FILE_2_REF = 12342;
-
-  private static final org.sonar.server.computation.component.Component FILE_1 = builder(org.sonar.server.computation.component.Component.Type.FILE, FILE_1_REF)
-    .setKey(FILE_1_KEY)
-    .build();
-
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule()
-    .setRoot(builder(org.sonar.server.computation.component.Component.Type.PROJECT, PROJECT_REF).setKey("project")
-      .addChildren(
-        FILE_1,
-        builder(org.sonar.server.computation.component.Component.Type.FILE, FILE_2_REF).setKey("fileKey2").build()
-      ).build());
-
-  @Rule
-  public MetricRepositoryRule metricRepository = new MetricRepositoryRule()
-    .add(1, CoreMetrics.NCLOC)
-    .add(new MetricImpl(2, INT_METRIC_KEY, "int metric", Metric.MetricType.INT))
-    .add(new MetricImpl(3, DOUBLE_METRIC_KEY, "double metric", Metric.MetricType.FLOAT))
-    .add(new MetricImpl(4, LONG_METRIC_KEY, "long metric", Metric.MetricType.MILLISEC))
-    .add(new MetricImpl(5, STRING_METRIC_KEY, "string metric", Metric.MetricType.STRING));
-
-  @Rule
-  public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
-
-  @Rule
-  public ComponentIssuesRepositoryRule componentIssuesRepository = new ComponentIssuesRepositoryRule(treeRootHolder);
-
-  SettingsRepository settingsRepository = mock(SettingsRepository.class);
-
-  @Test
-  public void get_component() throws Exception {
-    MeasureComputer.Implementation.Context underTest = newContext(FILE_1_REF);
-    assertThat(underTest.getComponent().getType()).isEqualTo(Component.Type.FILE);
-  }
-
-  @Test
-  public void get_string_settings() throws Exception {
-    org.sonar.api.config.Settings serverSettings = new org.sonar.api.config.Settings();
-    serverSettings.setProperty("prop", "value");
-    when(settingsRepository.getSettings(FILE_1)).thenReturn(serverSettings);
-
-    MeasureComputer.Implementation.Context underTest = newContext(FILE_1_REF);
-    assertThat(underTest.getSettings().getString("prop")).isEqualTo("value");
-    assertThat(underTest.getSettings().getString("unknown")).isNull();
-  }
-
-  @Test
-  public void get_string_array_settings() throws Exception {
-    org.sonar.api.config.Settings serverSettings = new org.sonar.api.config.Settings();
-    serverSettings.setProperty("prop", "1,3.4,8,50");
-    when(settingsRepository.getSettings(FILE_1)).thenReturn(serverSettings);
-
-    MeasureComputer.Implementation.Context underTest = newContext(FILE_1_REF);
-    assertThat(underTest.getSettings().getStringArray("prop")).containsExactly("1", "3.4", "8", "50");
-    assertThat(underTest.getSettings().getStringArray("unknown")).isEmpty();
-  }
-
-  @Test
-  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));
-    assertThat(underTest.getMeasure(NCLOC_KEY).getIntValue()).isEqualTo(10);
-  }
-
-  @Test
-  public void fail_with_IAE_when_get_measure_is_called_on_metric_not_in_input_list() throws Exception {
-    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"));
-    underTest.getMeasure(NCLOC_KEY);
-  }
-
-  @Test
-  public void get_children_measures() throws Exception {
-    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));
-    assertThat(underTest.getChildrenMeasures(NCLOC_KEY)).hasSize(2);
-    assertThat(underTest.getChildrenMeasures(NCLOC_KEY)).extracting("intValue").containsOnly(10, 12);
-  }
-
-  @Test
-  public void get_children_measures_when_one_child_has_no_value() throws Exception {
-    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));
-    assertThat(underTest.getChildrenMeasures(NCLOC_KEY)).extracting("intValue").containsOnly(10);
-  }
-
-  @Test
-  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));
-    assertThat(underTest.getChildrenMeasures(INT_METRIC_KEY)).hasSize(1);
-    assertThat(underTest.getChildrenMeasures(INT_METRIC_KEY)).extracting("intValue").containsOnly(10);
-  }
-
-  @Test
-  public void fail_with_IAE_when_get_children_measures_is_called_on_metric_not_in_input_list() throws Exception {
-    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"));
-    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));
-    underTest.addMeasure(INT_METRIC_KEY, 10);
-
-    Optional<Measure> measure = measureRepository.getAddedRawMeasure(PROJECT_REF, INT_METRIC_KEY);
-    assertThat(measure).isPresent();
-    assertThat(measure.get().getIntValue()).isEqualTo(10);
-  }
-
-  @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));
-    underTest.addMeasure(DOUBLE_METRIC_KEY, 10d);
-
-    Optional<Measure> measure = measureRepository.getAddedRawMeasure(PROJECT_REF, DOUBLE_METRIC_KEY);
-    assertThat(measure).isPresent();
-    assertThat(measure.get().getDoubleValue()).isEqualTo(10d);
-  }
-
-  @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));
-    underTest.addMeasure(LONG_METRIC_KEY, 10L);
-
-    Optional<Measure> measure = measureRepository.getAddedRawMeasure(PROJECT_REF, LONG_METRIC_KEY);
-    assertThat(measure).isPresent();
-    assertThat(measure.get().getLongValue()).isEqualTo(10L);
-  }
-
-  @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));
-    underTest.addMeasure(STRING_METRIC_KEY, "data");
-
-    Optional<Measure> measure = measureRepository.getAddedRawMeasure(PROJECT_REF, STRING_METRIC_KEY);
-    assertThat(measure).isPresent();
-    assertThat(measure.get().getStringValue()).isEqualTo("data");
-  }
-
-  @Test
-  public void fail_with_IAE_when_add_measure_is_called_on_metric_not_in_output_list() throws Exception {
-    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));
-    underTest.addMeasure(DOUBLE_METRIC_KEY, 10);
-  }
-
-  @Test
-  public void fail_with_unsupported_operation_when_adding_measure_that_already_exists() throws Exception {
-    thrown.expect(UnsupportedOperationException.class);
-    thrown.expectMessage("A measure on metric 'int_metric_key' already exists on component 'fileKey'");
-
-    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));
-    underTest.addMeasure(INT_METRIC_KEY, 10);
-  }
-
-  @Test
-  public void get_issues() throws Exception {
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("KEY")
-      .setRuleKey(RuleKey.of("xoo", "S01"))
-      .setSeverity("MAJOR")
-      .setStatus("CLOSED")
-      .setResolution("FIXED")
-      .setDebt(Duration.create(10l));
-
-    MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, Arrays.asList(issue));
-
-    assertThat(underTest.getIssues()).hasSize(1);
-    org.sonar.api.ce.measure.Issue result = underTest.getIssues().get(0);
-    assertThat(result.key()).isEqualTo("KEY");
-    assertThat(result.ruleKey()).isEqualTo(RuleKey.of("xoo", "S01"));
-    assertThat(result.severity()).isEqualTo("MAJOR");
-    assertThat(result.status()).isEqualTo("CLOSED");
-    assertThat(result.resolution()).isEqualTo("FIXED");
-    assertThat(result.debt()).isEqualTo(Duration.create(10l));
-  }
-
-  private MeasureComputer.Implementation.Context newContext(int componentRef) {
-    return newContext(componentRef, Collections.<String>emptySet(), Collections.<String>emptySet());
-  }
-
-  private MeasureComputer.Implementation.Context newContext(int componentRef, List<DefaultIssue> issues) {
-    return newContext(componentRef, Collections.<String>emptySet(), Collections.<String>emptySet(), 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 MeasureComputer.Implementation.Context newContext(int componentRef, final Set<String> inputMetrics, final Set<String> outputMetrics, 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,
-      settingsRepository, measureRepository, metricRepository, componentIssuesRepository);
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContextTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContextTest.java
deleted file mode 100644 (file)
index d68eacd..0000000
+++ /dev/null
@@ -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();
-  }
-
-}
index aab334c6818dab4dcffc8041bd29823e55711ec8..2b3d0163cedbd30fa590b7af855d98cf3fd055e0 100644 (file)
 
 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 {
index 9565c9417c3beaf58b29fbba4f9b88892c73647d..66bc873a3eec8f2392a1be5a73edcda7a1753a64 100644 (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
index ede0cce0b210e88ddc85419ab45da5d4d3153518..f94098414e942c177d918d7fbd4b957eb7603a42 100644 (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 {
index e4233c2a6a50ba6dd23a8766f6354ddf4838697f..7b68843bf941a33dbbdffd5fb1108222b5bbcf39 100644 (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();
index fea604b8ff3b03cd8f0950a35e3441ca737ea1ed..948f33eee6c04d7bc879c2880578026fdc8cface 100644 (file)
 
 package org.sonar.api.ce.measure;
 
+/**
+ * Measure used in {@link MeasureComputer}
+ *
+ * @since 5.2
+ */
 public interface Measure {
 
   /**
index d62851006a623bc771ccbc9a75e07942ca3354c5..76a8dfa5c96d4ccc70f7dacdbf6f831743ae9585 100644 (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();
   }
 }
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/MeasureComputerProvider.java b/sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/MeasureComputerProvider.java
deleted file mode 100644 (file)
index 6eb62df..0000000
+++ /dev/null
@@ -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();
-
-  }
-}
index 06408466db6545585c08f37e10679f9393414e5b..3910d777acf8ca611e44cd5c8de43aaf9301402d 100644 (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 {
 
index b704d6cd8e3a0db8098b6f7d8ae933b4ab93789d..8403c254fbf67c8d2e9e52d0d3f787f90373925d 100644 (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 {