]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6730 Compute plugin measures in CE 460/head
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 7 Aug 2015 12:03:19 +0000 (14:03 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 7 Aug 2015 12:09:51 +0000 (14:09 +0200)
20 files changed:
server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java
server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolder.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolderImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/ComponentImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContext.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContext.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputePluginMeasuresStep.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/FeedMeasureComputers.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureComputersHolderImplTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/ComponentImplTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContextTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContextTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureImplTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputePluginMeasuresStepTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedMeasureComputersTest.java [new file with mode: 0644]

index eeba9f111e8c4c1e815564f84e2dc3975129852c..0a0b20c2b20dbb0a8987ae143f02390de1334d09 100644 (file)
@@ -76,6 +76,7 @@ import org.sonar.server.computation.issue.commonrule.LineCoverageRule;
 import org.sonar.server.computation.issue.commonrule.SkippedTestRule;
 import org.sonar.server.computation.issue.commonrule.TestErrorRule;
 import org.sonar.server.computation.language.LanguageRepositoryImpl;
+import org.sonar.server.computation.measure.MeasureComputersHolderImpl;
 import org.sonar.server.computation.measure.MeasureRepositoryImpl;
 import org.sonar.server.computation.measure.newcoverage.NewCoverageMetricKeysModule;
 import org.sonar.server.computation.metric.MetricModule;
@@ -176,11 +177,11 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co
     return Arrays.asList(
       new ComputationTempFolderProvider(),
 
-    ActivityManager.class,
+      ActivityManager.class,
 
-    MetricModule.class,
+      MetricModule.class,
 
-    // holders
+      // holders
       BatchReportDirectoryHolderImpl.class,
       TreeRootHolderImpl.class,
       PeriodsHolderImpl.class,
@@ -188,10 +189,11 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co
       DebtModelHolderImpl.class,
       SqaleRatingSettings.class,
       ActiveRulesHolderImpl.class,
+      MeasureComputersHolderImpl.class,
 
-    BatchReportReaderImpl.class,
+      BatchReportReaderImpl.class,
 
-    // repositories
+      // repositories
       LanguageRepositoryImpl.class,
       MeasureRepositoryImpl.class,
       EventRepositoryImpl.class,
@@ -200,10 +202,10 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co
       QualityGateServiceImpl.class,
       EvaluationResultTextConverterImpl.class,
 
-    // new coverage measures
+      // new coverage measures
       NewCoverageMetricKeysModule.class,
 
-    // issues
+      // issues
       RuleCacheLoader.class,
       RuleRepositoryImpl.class,
       ScmAccountToUserLoader.class,
@@ -213,7 +215,7 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co
       IssueVisitors.class,
       IssueLifecycle.class,
 
-    // common rules
+      // common rules
       CommonRuleEngineImpl.class,
       BranchCoverageRule.class,
       LineCoverageRule.class,
@@ -222,7 +224,7 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co
       TestErrorRule.class,
       SkippedTestRule.class,
 
-    // order is important: DebtAggregator then NewDebtAggregator (new debt requires debt)
+      // order is important: DebtAggregator then NewDebtAggregator (new debt requires debt)
       DebtCalculator.class,
       DebtAggregator.class,
       NewDebtCalculator.class,
@@ -231,17 +233,17 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co
       RuleTagsCopier.class,
       IssueCounter.class,
 
-    UpdateConflictResolver.class,
+      UpdateConflictResolver.class,
       TrackerBaseInputFactory.class,
       TrackerRawInputFactory.class,
       Tracker.class,
       TrackerExecution.class,
       BaseIssuesLoader.class,
 
-    // views
+      // views
       ViewIndex.class,
 
-    // ComputationService
+      // ComputationService
       ComputationService.class);
   }
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolder.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolder.java
new file mode 100644 (file)
index 0000000..61a1ac3
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import org.sonar.api.ce.measure.MeasureComputer;
+
+public interface MeasureComputersHolder {
+
+  /**
+   * Return the list of measure computers sorted by the way they should be executed
+   *
+   * @throws IllegalStateException if the measure computers haven't been initialized
+   */
+  Iterable<MeasureComputer> getMeasureComputers();
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolderImpl.java
new file mode 100644 (file)
index 0000000..1bec032
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import com.google.common.base.Predicates;
+import javax.annotation.CheckForNull;
+import org.sonar.api.ce.measure.MeasureComputer;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.FluentIterable.from;
+import static java.util.Objects.requireNonNull;
+
+public class MeasureComputersHolderImpl implements MeasureComputersHolder {
+
+  @CheckForNull
+  private Iterable<MeasureComputer> measureComputers;
+
+  @Override
+  public Iterable<MeasureComputer> getMeasureComputers() {
+    checkState(this.measureComputers != null, "Measure computers have not been initialized yet");
+    return measureComputers;
+  }
+
+  /**
+   * Initializes the measure computers in the holder.
+   *
+   * @throws NullPointerException if the specified List is {@code null}
+   * @throws IllegalStateException if the holder has already been initialized
+   */
+  public void setMeasureComputers(Iterable<MeasureComputer> 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();
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/ComponentImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/ComponentImpl.java
new file mode 100644 (file)
index 0000000..5578afd
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * 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.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.ce.measure.Component;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public class ComponentImpl implements Component {
+
+  private final String key;
+
+  private final Type type;
+
+  @CheckForNull
+  private final FileAttributes fileAttributes;
+
+  public ComponentImpl(String key, Type type, @Nullable FileAttributes fileAttributes) {
+    this.key = requireNonNull(key, "Key cannot be null");
+    this.type = requireNonNull(type, "Type cannot be null");
+    this.fileAttributes = checkFileAttributes(fileAttributes);
+  }
+
+  @CheckForNull
+  private FileAttributes checkFileAttributes(@Nullable FileAttributes fileAttributes) {
+    if (fileAttributes == null && type == Type.FILE) {
+      throw new IllegalArgumentException("Component of type FILE must have a FileAttributes object");
+    } else if (fileAttributes != null && type != Type.FILE) {
+      throw new IllegalArgumentException("Only component of type FILE have a FileAttributes object");
+    }
+    return fileAttributes;
+  }
+
+  @Override
+  public Type getType() {
+    return type;
+  }
+
+  @Override
+  public FileAttributes getFileAttributes() {
+    checkState(this.type == Component.Type.FILE, "Only component of type FILE have a FileAttributes object");
+    return fileAttributes;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    ComponentImpl component = (ComponentImpl) o;
+
+    return key.equals(component.key);
+  }
+
+  @Override
+  public int hashCode() {
+    return key.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return "ComponentImpl{" +
+      "key=" + key +
+      ", type='" + type + '\'' +
+      ", fileAttributes=" + fileAttributes +
+      '}';
+  }
+
+  @Immutable
+  public static class FileAttributesImpl implements FileAttributes {
+
+    private final boolean unitTest;
+    private final String languageKey;
+
+    public FileAttributesImpl(@Nullable String languageKey, boolean unitTest) {
+      this.languageKey = languageKey;
+      this.unitTest = unitTest;
+    }
+
+    @Override
+    public boolean isUnitTest() {
+      return unitTest;
+    }
+
+    @Override
+    @CheckForNull
+    public String getLanguageKey() {
+      return languageKey;
+    }
+
+    @Override
+    public String toString() {
+      return "FileAttributesImpl{" +
+        "languageKey='" + languageKey + '\'' +
+        ", unitTest=" + unitTest +
+        '}';
+    }
+  }
+}
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
new file mode 100644 (file)
index 0000000..02ac275
--- /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 org.sonar.api.ce.measure.MeasureComputer;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+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;
+    private String[] outputMetrics;
+    private Implementation measureComputerImplementation;
+
+    @Override
+    public MeasureComputerBuilder setInputMetrics(String... inputMetrics) {
+      checkArgument(inputMetrics != null && inputMetrics.length > 0, "At least one input metric must be defined");
+      this.inputMetricKeys = inputMetrics;
+      return this;
+    }
+
+    @Override
+    public MeasureComputerBuilder setOutputMetrics(String... outputMetrics) {
+      checkArgument(outputMetrics != null && outputMetrics.length > 0, "At least one output metric must be defined");
+      this.outputMetrics = outputMetrics;
+      return this;
+    }
+
+    @Override
+    public MeasureComputerBuilder setImplementation(Implementation impl) {
+      checkImplementation(impl);
+      this.measureComputerImplementation = impl;
+      return this;
+    }
+
+    @Override
+    public MeasureComputer build() {
+      checkArgument(this.inputMetricKeys != null, "At least one input metric must be defined");
+      checkArgument(this.outputMetrics != null, "At least one output metric must be defined");
+      checkImplementation(this.measureComputerImplementation);
+      return new MeasureComputerImpl(this);
+    }
+
+    private static void checkImplementation(Implementation impl) {
+      checkArgument(impl != null, "The implementation is missing");
+    }
+  }
+}
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
new file mode 100644 (file)
index 0000000..b710678
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.computation.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.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.Measure;
+import org.sonar.api.ce.measure.MeasureComputer;
+import org.sonar.api.ce.measure.Settings;
+import org.sonar.server.computation.component.ProjectSettingsRepository;
+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 ProjectSettingsRepository settings;
+  private final MeasureRepository measureRepository;
+  private final MetricRepository metricRepository;
+
+  private final org.sonar.server.computation.component.Component internalComponent;
+  private final Component component;
+
+  private final Set<String> allowedMetrics;
+
+  public MeasureComputerImplementationContext(org.sonar.server.computation.component.Component component, MeasureComputer measureComputer,
+    ProjectSettingsRepository settings, MeasureRepository measureRepository, MetricRepository metricRepository) {
+    this.measureComputer = measureComputer;
+    this.settings = settings;
+    this.internalComponent = component;
+    this.measureRepository = measureRepository;
+    this.metricRepository = metricRepository;
+    this.component = newComponent(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.getProjectSettings(internalComponent.getKey());
+  }
+
+  @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", measureComputer.getOutputMetrics());
+    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()));
+    }
+  }
+
+  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;
+    }
+
+    @Nullable
+    @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
new file mode 100644 (file)
index 0000000..27be1a2
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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/MeasureImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureImpl.java
new file mode 100644 (file)
index 0000000..285a05a
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * 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.Locale;
+import java.util.Set;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.ce.measure.Measure;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+import static org.sonar.server.computation.measure.Measure.ValueType.DOUBLE;
+import static org.sonar.server.computation.measure.Measure.ValueType.INT;
+import static org.sonar.server.computation.measure.Measure.ValueType.LONG;
+import static org.sonar.server.computation.measure.Measure.ValueType.STRING;
+
+@Immutable
+public class MeasureImpl implements Measure {
+
+  private static final Set<org.sonar.server.computation.measure.Measure.ValueType> ALLOWED_VALUE_TYPES = ImmutableSet.of(INT, LONG, DOUBLE, STRING);
+
+  private final org.sonar.server.computation.measure.Measure measure;
+
+  public MeasureImpl(org.sonar.server.computation.measure.Measure measure) {
+    this.measure = requireNonNull(measure, "Measure couldn't be null");
+    checkState(ALLOWED_VALUE_TYPES.contains(measure.getValueType()), String.format("Only following types are allowed %s", ALLOWED_VALUE_TYPES));
+  }
+
+  @Override
+  public int getIntValue() {
+    checkValueType(INT);
+    return measure.getIntValue();
+  }
+
+  @Override
+  public long getLongValue() {
+    checkValueType(LONG);
+    return measure.getLongValue();
+  }
+
+  @Override
+  public double getDoubleValue() {
+    checkValueType(DOUBLE);
+    return measure.getDoubleValue();
+  }
+
+  @Override
+  public String getStringValue() {
+    checkValueType(STRING);
+    return measure.getStringValue();
+  }
+
+  private void checkValueType(org.sonar.server.computation.measure.Measure.ValueType expected) {
+    checkState(measure.getValueType() == expected, String.format(
+      "Value can not be converted to %s because current value type is a %s",
+      expected.toString().toLowerCase(Locale.US),
+      measure.getValueType()
+      ));
+  }
+
+  @Override
+  public String toString() {
+    return "MeasureImpl{" +
+      "measure=" + measure +
+      '}';
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/package-info.java
new file mode 100644 (file)
index 0000000..c96a98a
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.computation.measure.api;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index 22079297a05a418abbabc526242e668586477b34..ed0d26b02868879edaa2b7447c8c7c4d1d6114f6 100644 (file)
@@ -67,6 +67,9 @@ public class ComputationSteps {
       // SQALE measures depend on issues
       SqaleMeasuresStep.class,
 
+      FeedMeasureComputers.class,
+      ComputePluginMeasuresStep.class,
+
       // Must be executed after computation of all measures
       FillMeasuresWithVariationsStep.class,
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputePluginMeasuresStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputePluginMeasuresStep.java
new file mode 100644 (file)
index 0000000..f336a59
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.computation.step;
+
+import org.sonar.api.ce.measure.MeasureComputer;
+import org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor;
+import org.sonar.server.computation.component.ProjectSettingsRepository;
+import org.sonar.server.computation.component.TreeRootHolder;
+import org.sonar.server.computation.measure.MeasureComputersHolder;
+import org.sonar.server.computation.measure.MeasureRepository;
+import org.sonar.server.computation.measure.api.MeasureComputerImplementationContext;
+import org.sonar.server.computation.metric.MetricRepository;
+
+import static org.sonar.server.computation.component.Component.Type.FILE;
+import static org.sonar.server.computation.component.ComponentVisitor.Order.PRE_ORDER;
+
+public class ComputePluginMeasuresStep implements ComputationStep {
+
+  private final TreeRootHolder treeRootHolder;
+  private final MetricRepository metricRepository;
+  private final MeasureRepository measureRepository;
+  private final ProjectSettingsRepository settings;
+
+  private final MeasureComputersHolder measureComputersHolder;
+
+  public ComputePluginMeasuresStep(TreeRootHolder treeRootHolder, MetricRepository metricRepository, MeasureRepository measureRepository, ProjectSettingsRepository settings,
+    MeasureComputersHolder measureComputersHolder) {
+    this.treeRootHolder = treeRootHolder;
+    this.metricRepository = metricRepository;
+    this.measureRepository = measureRepository;
+    this.settings = settings;
+    this.measureComputersHolder = measureComputersHolder;
+  }
+
+  @Override
+  public void execute() {
+    new NewMetricDefinitionsVisitor().visit(treeRootHolder.getRoot());
+  }
+
+  private class NewMetricDefinitionsVisitor extends DepthTraversalTypeAwareVisitor {
+
+    public NewMetricDefinitionsVisitor() {
+      super(FILE, PRE_ORDER);
+    }
+
+    @Override
+    public void visitAny(org.sonar.server.computation.component.Component component) {
+      for (MeasureComputer computer : measureComputersHolder.getMeasureComputers()) {
+        MeasureComputerImplementationContext measureComputerContext = new MeasureComputerImplementationContext(component, computer, settings, measureRepository, metricRepository);
+        computer.getImplementation().compute(measureComputerContext);
+      }
+    }
+  }
+
+  @Override
+  public String getDescription() {
+    return "Compute measures from plugin";
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/FeedMeasureComputers.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/FeedMeasureComputers.java
new file mode 100644 (file)
index 0000000..36467e7
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.computation.step;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.collect.FluentIterable;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+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.MeasureComputersHolderImpl;
+import org.sonar.server.computation.measure.api.MeasureComputerProviderContext;
+
+public class FeedMeasureComputers implements ComputationStep {
+
+  private static final Set<String> CORE_METRIC_KEYS = FluentIterable.from(CoreMetrics.getMetrics()).transform(MetricToKey.INSTANCE).toSet();
+
+  private final MeasureComputersHolderImpl measureComputersHolder;
+  private final Metrics[] metricsRepositories;
+  private final MeasureComputerProvider[] measureComputerProviders;
+
+  public FeedMeasureComputers(MeasureComputersHolderImpl measureComputersHolder, Metrics[] metricsRepositories, MeasureComputerProvider[] measureComputerProviders) {
+    this.measureComputersHolder = measureComputersHolder;
+    this.measureComputerProviders = measureComputerProviders;
+    this.metricsRepositories = metricsRepositories;
+  }
+
+  /**
+   * Constructor override used by Pico to instantiate the class when no plugin is defining metrics
+   */
+  public FeedMeasureComputers(MeasureComputersHolderImpl measureComputersHolder, MeasureComputerProvider[] measureComputerProviders) {
+    this(measureComputersHolder, new Metrics[] {}, measureComputerProviders);
+  }
+
+  /**
+   * Constructor override used by Pico to instantiate the class when no plugin is defining measure computers
+   */
+  public FeedMeasureComputers(MeasureComputersHolderImpl measureComputersHolder, Metrics[] metricsRepositories) {
+    this(measureComputersHolder, metricsRepositories, new MeasureComputerProvider[] {});
+  }
+
+  /**
+   * Constructor override used by Pico to instantiate the class when no plugin is defining metrics neither measure computers
+   */
+  public FeedMeasureComputers(MeasureComputersHolderImpl measureComputersHolder) {
+    this(measureComputersHolder, new Metrics[] {}, new MeasureComputerProvider[] {});
+  }
+
+  @Override
+  public void execute() {
+    MeasureComputerProviderContext context = new MeasureComputerProviderContext();
+    for (MeasureComputerProvider provider : measureComputerProviders) {
+      provider.register(context);
+    }
+    validateInputMetrics(context.getMeasureComputers());
+    measureComputersHolder.setMeasureComputers(sortComputers(context.getMeasureComputers()));
+  }
+
+  private static Iterable<MeasureComputer> sortComputers(List<MeasureComputer> computers) {
+    Map<String, MeasureComputer> computersByOutputMetric = new HashMap<>();
+    Map<String, MeasureComputer> computersByInputMetric = new HashMap<>();
+    for (MeasureComputer computer : computers) {
+      for (String outputMetric : computer.getOutputMetrics()) {
+        computersByOutputMetric.put(outputMetric, computer);
+      }
+      for (String inputMetric : computer.getInputMetrics()) {
+        computersByInputMetric.put(inputMetric, computer);
+      }
+    }
+    ToComputerByKey toComputerByOutputMetricKey = new ToComputerByKey(computersByOutputMetric);
+    ToComputerByKey toComputerByInputMetricKey = new ToComputerByKey(computersByInputMetric);
+
+    DirectAcyclicGraph dag = new DirectAcyclicGraph();
+    for (MeasureComputer computer : computers) {
+      dag.add(computer);
+      for (MeasureComputer dependency : getDependencies(computer, toComputerByOutputMetricKey)) {
+        dag.add(computer, dependency);
+      }
+      for (MeasureComputer generates : getDependents(computer, toComputerByInputMetricKey)) {
+        dag.add(generates, computer);
+      }
+    }
+    return FluentIterable.from(dag.sort());
+  }
+
+  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 static Iterable<MeasureComputer> getDependencies(MeasureComputer 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());
+  }
+
+  private static Iterable<MeasureComputer> getDependents(MeasureComputer measureComputer, ToComputerByKey toComputerByInputMetricKey) {
+    return FluentIterable.from(measureComputer.getInputMetrics()).transform(toComputerByInputMetricKey);
+  }
+
+  private static class ToComputerByKey implements Function<String, MeasureComputer> {
+    private final Map<String, MeasureComputer> computersByMetric;
+
+    private ToComputerByKey(Map<String, MeasureComputer> computersByMetric) {
+      this.computersByMetric = computersByMetric;
+    }
+
+    @Override
+    public MeasureComputer apply(@Nonnull String metricKey) {
+      return computersByMetric.get(metricKey);
+    }
+  }
+
+  private enum MetricToKey implements Function<Metric, String> {
+    INSTANCE;
+
+    @Nullable
+    @Override
+    public String apply(@Nonnull Metric input) {
+      return input.key();
+    }
+  }
+
+  private enum MetricsToMetricList implements Function<Metrics, List<Metric>> {
+    INSTANCE;
+
+    @Override
+    public List<Metric> apply(@Nonnull Metrics input) {
+      return input.getMetrics();
+    }
+  }
+
+  @Override
+  public String getDescription() {
+    return "Feed measure computers";
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureComputersHolderImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureComputersHolderImplTest.java
new file mode 100644 (file)
index 0000000..a42ce3e
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableList;
+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 static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class MeasureComputersHolderImplTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  private MeasureComputersHolderImpl underTest = new MeasureComputersHolderImpl();
+
+  @Test
+  public void get_measure_computers() throws Exception {
+    MeasureComputer measureComputer = mock(MeasureComputer.class);
+    underTest.setMeasureComputers(Collections.singletonList(measureComputer));
+
+    assertThat(underTest.getMeasureComputers()).containsOnly(measureComputer);
+  }
+
+  @Test
+  public void get_measure_computers_throws_ISE_if_not_initialized() {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Measure computers have not been initialized yet");
+
+    underTest.getMeasureComputers();
+  }
+
+  @Test
+  public void set_measure_computers_supports_empty_arg_is_empty() {
+    underTest.setMeasureComputers(ImmutableList.<MeasureComputer>of());
+
+    assertThat(underTest.getMeasureComputers()).isEmpty();
+  }
+
+  @Test
+  public void set_measure_computers_throws_ISE_if_already_initialized() {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Measure computers have already been initialized");
+
+    MeasureComputer measureComputer = mock(MeasureComputer.class);
+    underTest.setMeasureComputers(Collections.singletonList(measureComputer));
+    underTest.setMeasureComputers(Collections.singletonList(measureComputer));
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/ComponentImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/ComponentImplTest.java
new file mode 100644 (file)
index 0000000..6c133a2
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * 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.Component;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ComponentImplTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void create_project() throws Exception {
+    ComponentImpl component = new ComponentImpl("Project", Component.Type.PROJECT, null);
+
+    assertThat(component.getType()).isEqualTo(Component.Type.PROJECT);
+  }
+
+  @Test
+  public void create_source_file() throws Exception {
+    ComponentImpl component = new ComponentImpl("File", Component.Type.FILE, new ComponentImpl.FileAttributesImpl("xoo", false));
+
+    assertThat(component.getType()).isEqualTo(Component.Type.FILE);
+    assertThat(component.getFileAttributes().getLanguageKey()).isEqualTo("xoo");
+    assertThat(component.getFileAttributes().isUnitTest()).isFalse();
+  }
+
+  @Test
+  public void create_test_file() throws Exception {
+    ComponentImpl component = new ComponentImpl("File", Component.Type.FILE, new ComponentImpl.FileAttributesImpl(null, true));
+
+    assertThat(component.getType()).isEqualTo(Component.Type.FILE);
+    assertThat(component.getFileAttributes().isUnitTest()).isTrue();
+    assertThat(component.getFileAttributes().getLanguageKey()).isNull();
+  }
+
+  @Test
+  public void fail_with_ISE_when_calling_get_file_attributes_on_not_file() throws Exception {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Only component of type FILE have a FileAttributes object");
+
+    ComponentImpl component = new ComponentImpl("Project", Component.Type.PROJECT, null);
+    component.getFileAttributes();
+  }
+
+  @Test
+  public void fail_with_IAE_when_trying_to_create_a_file_without_file_attributes() throws Exception {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("omponent of type FILE must have a FileAttributes object");
+
+    new ComponentImpl("File", Component.Type.FILE, null);
+  }
+
+  @Test
+  public void fail_with_IAE_when_trying_to_create_not_a_file_with_file_attributes() throws Exception {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Only component of type FILE have a FileAttributes object");
+
+    new ComponentImpl("Project", Component.Type.PROJECT, new ComponentImpl.FileAttributesImpl(null, true));
+  }
+
+  @Test
+  public void fail_with_NPE_when_creating_component_without_key() throws Exception {
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("Key cannot be null");
+
+    new ComponentImpl(null, Component.Type.PROJECT, null);
+  }
+
+  @Test
+  public void fail_with_NPE_when_creating_component_without_type() throws Exception {
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("Type cannot be null");
+
+    new ComponentImpl("Project", null, null);
+  }
+
+  @Test
+  public void test_equals_and_hashcode() throws Exception {
+    ComponentImpl component = new ComponentImpl("Project1", Component.Type.PROJECT, null);
+    ComponentImpl sameComponent = new ComponentImpl("Project1", Component.Type.PROJECT, null);
+    ComponentImpl anotherComponent = new ComponentImpl("Project2", Component.Type.PROJECT, null);
+
+    assertThat(component).isEqualTo(component);
+    assertThat(component).isEqualTo(sameComponent);
+    assertThat(component).isNotEqualTo(anotherComponent);
+    assertThat(component).isNotEqualTo(null);
+
+    assertThat(component.hashCode()).isEqualTo(component.hashCode());
+    assertThat(component.hashCode()).isEqualTo(sameComponent.hashCode());
+    assertThat(component.hashCode()).isNotEqualTo(anotherComponent.hashCode());
+  }
+
+  @Test
+  public void test_to_string() throws Exception {
+    assertThat(new ComponentImpl("File", Component.Type.FILE, new ComponentImpl.FileAttributesImpl("xoo", true)).toString())
+      .isEqualTo("ComponentImpl{key=File, type='FILE', fileAttributes=FileAttributesImpl{languageKey='xoo', unitTest=true}}");
+  }
+}
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
new file mode 100644 (file)
index 0000000..a813d8e
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * 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 fail_with_IAE_when_no_input_metrics() throws Exception {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("At least one input metric must be defined");
+
+    new MeasureComputerImpl.MeasureComputerBuilderImpl()
+      .setOutputMetrics("comment_density_1", "comment_density_2")
+      .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION)
+      .build();
+  }
+
+  @Test
+  public void fail_with_IAE_when_null_input_metrics() throws Exception {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("At least one input metric must be defined");
+
+    new MeasureComputerImpl.MeasureComputerBuilderImpl()
+      .setInputMetrics(null)
+      .setOutputMetrics("comment_density_1", "comment_density_2")
+      .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION);
+  }
+
+  @Test
+  public void fail_with_IAE_with_empty_input_metrics() throws Exception {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("At least one input metric must be defined");
+
+    new MeasureComputerImpl.MeasureComputerBuilderImpl()
+      .setInputMetrics()
+      .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_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(IllegalArgumentException.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(IllegalArgumentException.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
new file mode 100644 (file)
index 0000000..de1e872
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * 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.Collections;
+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.server.computation.batch.TreeRootHolderRule;
+import org.sonar.server.computation.component.ProjectSettingsRepository;
+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.DumbComponent.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;
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule()
+    .setRoot(builder(org.sonar.server.computation.component.Component.Type.PROJECT, PROJECT_REF).setKey("project")
+      .addChildren(
+        builder(org.sonar.server.computation.component.Component.Type.FILE, FILE_1_REF).setKey(FILE_1_KEY).build(),
+        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);
+
+  ProjectSettingsRepository projectSettingsRepository = mock(ProjectSettingsRepository.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(projectSettingsRepository.getProjectSettings(FILE_1_KEY)).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(projectSettingsRepository.getProjectSettings(FILE_1_KEY)).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");
+
+    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);
+  }
+
+  private MeasureComputer.Implementation.Context newContext(int componentRef) {
+    return newContext(componentRef, Collections.<String>emptySet(), Collections.<String>emptySet());
+  }
+
+  private MeasureComputer.Implementation.Context newContext(int componentRef, final Set<String> inputMetrics, final Set<String> outputMetrics) {
+    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, projectSettingsRepository, measureRepository, metricRepository);
+  }
+}
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
new file mode 100644 (file)
index 0000000..d68eacd
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * 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();
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureImplTest.java
new file mode 100644 (file)
index 0000000..cd7d24d
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.computation.measure.api;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.server.computation.measure.Measure;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MeasureImplTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void get_int_value() throws Exception {
+    MeasureImpl measure = new MeasureImpl(Measure.newMeasureBuilder().create(1));
+    assertThat(measure.getIntValue()).isEqualTo(1);
+  }
+
+  @Test
+  public void fail_with_ISE_when_not_int_value() throws Exception {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Value can not be converted to int because current value type is a DOUBLE");
+
+    MeasureImpl measure = new MeasureImpl(Measure.newMeasureBuilder().create(1d));
+    measure.getIntValue();
+  }
+
+  @Test
+  public void get_double_value() throws Exception {
+    MeasureImpl measure = new MeasureImpl(Measure.newMeasureBuilder().create(1d));
+    assertThat(measure.getDoubleValue()).isEqualTo(1d);
+  }
+
+  @Test
+  public void fail_with_ISE_when_not_double_value() throws Exception {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Value can not be converted to double because current value type is a INT");
+
+    MeasureImpl measure = new MeasureImpl(Measure.newMeasureBuilder().create(1));
+    measure.getDoubleValue();
+  }
+
+  @Test
+  public void get_long_value() throws Exception {
+    MeasureImpl measure = new MeasureImpl(Measure.newMeasureBuilder().create(1L));
+    assertThat(measure.getLongValue()).isEqualTo(1L);
+  }
+
+  @Test
+  public void fail_with_ISE_when_not_long_value() throws Exception {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Value can not be converted to long because current value type is a STRING");
+
+    MeasureImpl measure = new MeasureImpl(Measure.newMeasureBuilder().create("value"));
+    measure.getLongValue();
+  }
+
+  @Test
+  public void get_string_value() throws Exception {
+    MeasureImpl measure = new MeasureImpl(Measure.newMeasureBuilder().create("value"));
+    assertThat(measure.getStringValue()).isEqualTo("value");
+  }
+
+  @Test
+  public void fail_with_ISE_when_not_string_value() throws Exception {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Value can not be converted to string because current value type is a LONG");
+
+    MeasureImpl measure = new MeasureImpl(Measure.newMeasureBuilder().create(1L));
+    measure.getStringValue();
+  }
+
+  @Test
+  public void fail_with_ISE_when_creating_measure_with_no_value() throws Exception {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Only following types are allowed [INT, LONG, DOUBLE, STRING]");
+
+    new MeasureImpl(Measure.newMeasureBuilder().createNoValue());
+  }
+
+  @Test
+  public void fail_with_ISE_when_creating_measure_with_not_allowed_value() throws Exception {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Only following types are allowed [INT, LONG, DOUBLE, STRING]");
+
+    new MeasureImpl(Measure.newMeasureBuilder().create(Measure.Level.ERROR));
+  }
+
+  @Test
+  public void fail_with_NPE_when_creating_measure_with_null_measure() throws Exception {
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("Measure couldn't be null");
+
+    new MeasureImpl(null);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputePluginMeasuresStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputePluginMeasuresStepTest.java
new file mode 100644 (file)
index 0000000..4b3c42f
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.computation.step;
+
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.ce.measure.Measure;
+import org.sonar.api.ce.measure.MeasureComputer;
+import org.sonar.server.computation.batch.TreeRootHolderRule;
+import org.sonar.server.computation.measure.MeasureComputersHolderImpl;
+import org.sonar.server.computation.measure.MeasureRepositoryRule;
+import org.sonar.server.computation.measure.api.MeasureComputerImpl;
+import org.sonar.server.computation.metric.MetricRepositoryRule;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.measures.CoreMetrics.COMMENT_LINES;
+import static org.sonar.api.measures.CoreMetrics.COMMENT_LINES_KEY;
+import static org.sonar.api.measures.CoreMetrics.NCLOC;
+import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
+import static org.sonar.server.computation.component.Component.Type.DIRECTORY;
+import static org.sonar.server.computation.component.Component.Type.FILE;
+import static org.sonar.server.computation.component.Component.Type.MODULE;
+import static org.sonar.server.computation.component.Component.Type.PROJECT;
+import static org.sonar.server.computation.component.DumbComponent.builder;
+import static org.sonar.server.computation.measure.Measure.newMeasureBuilder;
+import static org.sonar.server.computation.measure.MeasureRepoEntry.entryOf;
+import static org.sonar.server.computation.measure.MeasureRepoEntry.toEntries;
+
+public class ComputePluginMeasuresStepTest {
+
+  private static final String NEW_METRIC_KEY = "new_metric_key";
+  private static final String NEW_METRIC_NAME = "new metric name";
+
+  private static final org.sonar.api.measures.Metric<Integer> NEW_METRIC = new org.sonar.api.measures.Metric.Builder(NEW_METRIC_KEY, NEW_METRIC_NAME,
+    org.sonar.api.measures.Metric.ValueType.INT)
+    .create();
+
+  private static final int ROOT_REF = 1;
+  private static final int MODULE_REF = 12;
+  private static final int DIRECTORY_REF = 123;
+  private static final int FILE_1_REF = 1231;
+  private static final int FILE_2_REF = 1232;
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+
+  @Rule
+  public MetricRepositoryRule metricRepository = new MetricRepositoryRule()
+    .add(NCLOC)
+    .add(COMMENT_LINES)
+    .add(NEW_METRIC);
+
+  @Rule
+  public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
+
+  @Before
+  public void setUp() throws Exception {
+    treeRootHolder.setRoot(
+      builder(PROJECT, ROOT_REF).setKey("project")
+        .addChildren(
+          builder(MODULE, MODULE_REF).setKey("module")
+            .addChildren(
+              builder(DIRECTORY, DIRECTORY_REF).setKey("directory")
+                .addChildren(
+                  builder(FILE, FILE_1_REF).setKey("file1").build(),
+                  builder(FILE, FILE_2_REF).setKey("file2").build()
+                ).build()
+            ).build()
+        ).build());
+  }
+
+  MeasureComputersHolderImpl measureComputersHolder = new MeasureComputersHolderImpl();
+
+  @Test
+  public void compute_plugin_measure() throws Exception {
+    measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(10));
+    measureRepository.addRawMeasure(FILE_1_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(2));
+    measureRepository.addRawMeasure(FILE_2_REF, NCLOC_KEY, newMeasureBuilder().create(40));
+    measureRepository.addRawMeasure(FILE_2_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(5));
+    measureRepository.addRawMeasure(DIRECTORY_REF, NCLOC_KEY, newMeasureBuilder().create(50));
+    measureRepository.addRawMeasure(DIRECTORY_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(7));
+    measureRepository.addRawMeasure(MODULE_REF, NCLOC_KEY, newMeasureBuilder().create(50));
+    measureRepository.addRawMeasure(MODULE_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(7));
+    measureRepository.addRawMeasure(ROOT_REF, NCLOC_KEY, newMeasureBuilder().create(50));
+    measureRepository.addRawMeasure(ROOT_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(7));
+
+    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) {
+              Measure ncloc = ctx.getMeasure(NCLOC_KEY);
+              Measure comment = ctx.getMeasure(COMMENT_LINES_KEY);
+              if (ncloc != null && comment != null) {
+                ctx.addMeasure(NEW_METRIC_KEY, ncloc.getIntValue() + comment.getIntValue());
+              }
+            }
+          }
+        )
+        .build()
+      ));
+    ComputationStep underTest = new ComputePluginMeasuresStep(treeRootHolder, metricRepository, measureRepository, null, measureComputersHolder);
+    underTest.execute();
+
+    assertThat(toEntries(measureRepository.getAddedRawMeasures(FILE_1_REF))).containsOnly(entryOf(NEW_METRIC_KEY, newMeasureBuilder().create(12)));
+    assertThat(toEntries(measureRepository.getAddedRawMeasures(FILE_2_REF))).containsOnly(entryOf(NEW_METRIC_KEY, newMeasureBuilder().create(45)));
+    assertThat(toEntries(measureRepository.getAddedRawMeasures(DIRECTORY_REF))).containsOnly(entryOf(NEW_METRIC_KEY, newMeasureBuilder().create(57)));
+    assertThat(toEntries(measureRepository.getAddedRawMeasures(MODULE_REF))).containsOnly(entryOf(NEW_METRIC_KEY, newMeasureBuilder().create(57)));
+    assertThat(toEntries(measureRepository.getAddedRawMeasures(ROOT_REF))).containsOnly(entryOf(NEW_METRIC_KEY, newMeasureBuilder().create(57)));
+  }
+
+  @Test
+  public void nothing_to_compute() throws Exception {
+    measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(10));
+    measureRepository.addRawMeasure(FILE_1_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(2));
+    measureRepository.addRawMeasure(FILE_2_REF, NCLOC_KEY, newMeasureBuilder().create(40));
+    measureRepository.addRawMeasure(FILE_2_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(5));
+    measureRepository.addRawMeasure(DIRECTORY_REF, NCLOC_KEY, newMeasureBuilder().create(50));
+    measureRepository.addRawMeasure(DIRECTORY_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(7));
+    measureRepository.addRawMeasure(MODULE_REF, NCLOC_KEY, newMeasureBuilder().create(50));
+    measureRepository.addRawMeasure(MODULE_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(7));
+    measureRepository.addRawMeasure(ROOT_REF, NCLOC_KEY, newMeasureBuilder().create(50));
+    measureRepository.addRawMeasure(ROOT_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(7));
+
+    measureComputersHolder.setMeasureComputers(Collections.<MeasureComputer>emptyList());
+    ComputationStep underTest = new ComputePluginMeasuresStep(treeRootHolder, metricRepository, measureRepository, null, measureComputersHolder);
+    underTest.execute();
+
+    assertThat(toEntries(measureRepository.getAddedRawMeasures(FILE_1_REF))).isEmpty();
+    assertThat(toEntries(measureRepository.getAddedRawMeasures(FILE_2_REF))).isEmpty();
+    assertThat(toEntries(measureRepository.getAddedRawMeasures(DIRECTORY_REF))).isEmpty();
+    assertThat(toEntries(measureRepository.getAddedRawMeasures(MODULE_REF))).isEmpty();
+    assertThat(toEntries(measureRepository.getAddedRawMeasures(ROOT_REF))).isEmpty();
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedMeasureComputersTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedMeasureComputersTest.java
new file mode 100644 (file)
index 0000000..aab334c
--- /dev/null
@@ -0,0 +1,216 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.computation.step;
+
+import com.google.common.collect.Lists;
+import java.util.List;
+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 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.Metric.ValueType.DATA;
+import static org.sonar.api.measures.Metric.ValueType.FLOAT;
+import static org.sonar.api.measures.Metric.ValueType.INT;
+import static org.sonar.api.measures.Metric.ValueType.MILLISEC;
+
+public class FeedMeasureComputersTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  private static final String NEW_METRIC_1 = "metric1";
+  private static final String NEW_METRIC_2 = "metric2";
+  private static final String NEW_METRIC_3 = "metric3";
+  private static final String NEW_METRIC_4 = "metric4";
+
+  MeasureComputersHolderImpl holder = new MeasureComputersHolderImpl();
+
+  @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);
+    underTest.execute();
+
+    assertThat(holder.getMeasureComputers()).hasSize(1);
+  }
+
+  @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);
+    underTest.execute();
+
+    assertThat(holder.getMeasureComputers()).hasSize(1);
+  }
+
+  @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)
+    };
+
+    ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), providers);
+    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);
+  }
+
+  @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");
+
+    MeasureComputerProvider[] providers = new MeasureComputerProvider[] {
+      new NewMeasureComputerProvider(
+        array("unknown"),
+        array(NEW_METRIC_4),
+        mock(MeasureComputer.Implementation.class)),
+    };
+
+    ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), providers);
+    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);
+    underTest.execute();
+
+    assertThat(holder.getMeasureComputers()).hasSize(1);
+  }
+
+  @Test
+  public void return_empty_list_when_no_measure_computers() throws Exception {
+    ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()));
+    underTest.execute();
+
+    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);
+    underTest.execute();
+
+    assertThat(holder.getMeasureComputers()).isEmpty();
+  }
+
+  private static class NewMeasureComputerProvider implements MeasureComputerProvider {
+
+    private final String[] inputMetrics;
+    private final String[] outputMetrics;
+    private final MeasureComputer.Implementation measureComputerImplementation;
+
+    public NewMeasureComputerProvider(String[] inputMetrics, String[] outputMetrics, MeasureComputer.Implementation measureComputerImplementation) {
+      this.inputMetrics = inputMetrics;
+      this.outputMetrics = outputMetrics;
+      this.measureComputerImplementation = measureComputerImplementation;
+    }
+
+    @Override
+    public void register(Context ctx) {
+      ctx.add(ctx.newMeasureComputerBuilder()
+        .setInputMetrics(inputMetrics)
+        .setOutputMetrics(outputMetrics)
+        .setImplementation(measureComputerImplementation)
+        .build());
+    }
+  }
+
+  private static class TestMetrics implements Metrics {
+    @Override
+    public List<Metric> getMetrics() {
+      return Lists.<Metric>newArrayList(
+        new Metric.Builder(NEW_METRIC_1, "metric1", DATA).create(),
+        new Metric.Builder(NEW_METRIC_2, "metric2", MILLISEC).create(),
+        new Metric.Builder(NEW_METRIC_3, "metric3", INT).create(),
+        new Metric.Builder(NEW_METRIC_4, "metric4", FLOAT).create()
+        );
+    }
+  }
+
+}