]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5388 Introduce the SQALE Rating metric in SonarQube Core
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 9 Jul 2014 16:36:52 +0000 (18:36 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 9 Jul 2014 16:36:52 +0000 (18:36 +0200)
13 files changed:
sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingDecorator.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingGrid.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingSettings.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingDecoratorTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingGridTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingSettingsTest.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java
sonar-core/src/main/java/org/sonar/core/config/DebtProperties.java [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/l10n/core.properties
sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
sonar-plugin-api/src/test/java/org/sonar/api/resources/CoreMetricsTest.java

diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingDecorator.java
new file mode 100644 (file)
index 0000000..dc973dd
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * 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.batch.debt;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+
+import javax.annotation.Nullable;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Decorator that computes Sqale Rating metric
+ */
+public final class SqaleRatingDecorator implements Decorator {
+
+  private final SqaleRatingSettings sqaleRatingSettings;
+  private final Metric[] metrics;
+  private final FileSystem fs;
+
+  public SqaleRatingDecorator(SqaleRatingSettings sqaleRatingSettings, Metric[] metrics, FileSystem fs) {
+    this.sqaleRatingSettings = sqaleRatingSettings;
+    this.metrics = Arrays.copyOf(metrics, metrics.length);
+    this.fs = fs;
+  }
+
+  @VisibleForTesting
+  SqaleRatingDecorator() {
+    this.sqaleRatingSettings = null;
+    this.metrics = null;
+    this.fs = null;
+  }
+
+  public boolean shouldExecuteOnProject(Project project) {
+    return true;
+  }
+
+  @DependsUpon
+  public List<Metric> dependsOnMetrics() {
+    return Lists.<Metric>newArrayList(CoreMetrics.TECHNICAL_DEBT,
+      // ncloc and complexity are the two possible metrics to be used to calculate the development cost
+      CoreMetrics.NCLOC, CoreMetrics.COMPLEXITY);
+  }
+
+  @DependedUpon
+  public List<Metric> generatesMetrics() {
+    return Lists.<Metric>newArrayList(CoreMetrics.RATING, CoreMetrics.DEVELOPMENT_COST);
+  }
+
+  public void decorate(Resource resource, DecoratorContext context) {
+    if (ResourceUtils.isPersistable(resource) && !ResourceUtils.isUnitTestClass(resource)) {
+      Long developmentCost = getDevelopmentCost(context);
+      context.saveMeasure(new Measure(CoreMetrics.DEVELOPMENT_COST, Long.toString(developmentCost)));
+
+      long debt = getMeasureValue(context, CoreMetrics.TECHNICAL_DEBT);
+      double density = computeDensity(debt, developmentCost);
+      SqaleRatingGrid ratingGrid = new SqaleRatingGrid(sqaleRatingSettings.getRatingGrid());
+      context.saveMeasure(createRatingMeasure(ratingGrid.getRatingForDensity(density)));
+    }
+  }
+
+  private Measure createRatingMeasure(int rating) {
+    return new Measure(CoreMetrics.RATING).setIntValue(rating).setData(toRatingLetter(rating));
+  }
+
+  static String toRatingLetter(@Nullable Integer rating) {
+    if (rating != null) {
+      return SqaleRatingGrid.SqaleRating.buildFromIndex(rating).name();
+    }
+    return null;
+  }
+
+  private long getDevelopmentCost(DecoratorContext context) {
+    InputFile file = fs.inputFile(fs.predicates().hasRelativePath(context.getResource().getKey()));
+    if (file != null) {
+      String language = file.language();
+      return getMeasureValue(context, sqaleRatingSettings.getSizeMetric(language, metrics)) * sqaleRatingSettings.getWorkUnitsBySizePoint(language);
+    } else {
+      Collection<Measure> childrenMeasures = context.getChildrenMeasures(CoreMetrics.DEVELOPMENT_COST);
+      Double sum = sum(childrenMeasures);
+      return sum.longValue();
+    }
+  }
+
+  private static Double sum(@Nullable Collection<Measure> measures) {
+    if (measures == null) {
+      return 0d;
+    }
+    double sum = 0d;
+    for (Measure measure : measures) {
+      if (measure.getData() != null) {
+        sum += Double.parseDouble(measure.getData());
+      }
+    }
+    return sum;
+  }
+
+  private long getMeasureValue(DecoratorContext context, Metric metric) {
+    Measure measure = context.getMeasure(metric);
+    if (measure != null) {
+      return measure.getValue().longValue();
+    }
+    return 0;
+  }
+
+  protected double computeDensity(double debt, double developmentCost) {
+    if (developmentCost != 0) {
+      return debt / developmentCost;
+    }
+    return 0;
+  }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingGrid.java b/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingGrid.java
new file mode 100644 (file)
index 0000000..5db6ab6
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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.batch.debt;
+
+import org.sonar.api.utils.MessageException;
+
+import java.util.Arrays;
+
+class SqaleRatingGrid {
+
+  private final double[] gridValues;
+
+  public SqaleRatingGrid(double[] gridValues) {
+    this.gridValues = Arrays.copyOf(gridValues, gridValues.length);
+  }
+
+  public double getGradeLowerBound(SqaleRating rating) {
+    if (rating.getIndex() > 1) {
+      return gridValues[rating.getIndex() - 2];
+    }
+    return 0;
+  }
+
+  public int getRatingForDensity(double density) {
+    for (SqaleRating sqaleRating : SqaleRating.values()) {
+      double lowerBound = getGradeLowerBound(sqaleRating);
+      if (density >= lowerBound) {
+        return sqaleRating.getIndex();
+      }
+    }
+    throw MessageException.of("The SQALE density value should be between 0 and " + Double.MAX_VALUE + " and got " + density);
+  }
+
+  enum SqaleRating {
+
+    E(5),
+    D(4),
+    C(3),
+    B(2),
+    A(1);
+
+    private final int index;
+
+    private SqaleRating(int index) {
+      this.index = index;
+    }
+
+    public int getIndex() {
+      return index;
+    }
+
+    public static SqaleRating buildFromIndex(int index) {
+      for (SqaleRating rating : values()) {
+        if (rating.getIndex() == index) {
+          return rating;
+        }
+      }
+      throw new IllegalArgumentException("A SQALE rating must be in the range [1..5].");
+    }
+  }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingSettings.java b/sonar-batch/src/main/java/org/sonar/batch/debt/SqaleRatingSettings.java
new file mode 100644 (file)
index 0000000..acba026
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * 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.batch.debt;
+
+import org.sonar.api.BatchComponent;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
+import org.sonar.api.measures.Metric;
+
+public class SqaleRatingSettings implements BatchComponent {
+
+  private final Settings settings;
+
+  public SqaleRatingSettings(Settings settings) {
+    this.settings = settings;
+  }
+
+  public double[] getRatingGrid() {
+    try {
+      String[] ratingGrades = settings.getStringArray(CoreProperties.RATING_GRID);
+      double[] grid = new double[4];
+      for (int i = 0; i < 4; i++) {
+        grid[i] = Double.parseDouble(ratingGrades[i]);
+      }
+      return grid;
+    } catch (Exception e) {
+      throw new IllegalArgumentException("The SQALE rating grid is incorrect. Expected something similar to '"
+        + CoreProperties.RATING_GRID_DEF_VALUES + "' and got '"
+        + settings.getString(CoreProperties.RATING_GRID) + "'", e);
+    }
+  }
+
+  public long getWorkUnitsBySizePoint(String languageKey) {
+    try {
+      LanguageSpecificConfiguration languageSpecificConfig = getSpecificParametersForLanguage(languageKey);
+      if (languageSpecificConfig != null && languageSpecificConfig.getManDays() != null) {
+        return Long.parseLong(languageSpecificConfig.getManDays());
+      }
+      return Long.parseLong(settings.getString(CoreProperties.MAN_DAYS_BY_SIZE_POINT));
+    } catch (Exception e) {
+      throw new IllegalArgumentException("The value of the SQALE property '" + CoreProperties.MAN_DAYS_BY_SIZE_POINT
+        + "' is incorrect. Expected long but got '" + settings.getString(CoreProperties.MAN_DAYS_BY_SIZE_POINT) + "'", e);
+    }
+  }
+
+  public Metric getSizeMetric(String languageKey, Metric[] metrics) {
+    LanguageSpecificConfiguration languageSpecificConfig = getSpecificParametersForLanguage(languageKey);
+    if (languageSpecificConfig != null && languageSpecificConfig.getMetric() != null) {
+      return getMetricForKey(languageSpecificConfig.getMetric(), metrics);
+    }
+    return getMetricForKey(settings.getString(CoreProperties.SIZE_METRIC), metrics);
+  }
+
+  private Metric getMetricForKey(String sizeMetricKey, Metric[] metrics) {
+    for (Metric metric : metrics) {
+      if (metric.getKey().equals(sizeMetricKey)) {
+        return metric;
+      }
+    }
+    throw new IllegalArgumentException("The metric key used to define the SQALE size metric is unknown : '" + sizeMetricKey + "'");
+  }
+
+  private LanguageSpecificConfiguration getSpecificParametersForLanguage(String languageKey) {
+    String[] languageConfigIndexes = settings.getStringArray(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS);
+    for (String languageConfigIndex : languageConfigIndexes) {
+      String languagePropertyKey = CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + languageConfigIndex + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY;
+      if (languageKey.equals(settings.getString(languagePropertyKey))) {
+        return LanguageSpecificConfiguration.create(settings, languageConfigIndex);
+      }
+    }
+    return null;
+  }
+
+  private static class LanguageSpecificConfiguration {
+
+    private final String language;
+    private final String manDays;
+    private final String metric;
+
+    private LanguageSpecificConfiguration(String language, String manDays, String metric) {
+      this.language = language;
+      this.manDays = manDays;
+      this.metric = metric;
+    }
+
+    static LanguageSpecificConfiguration create(Settings settings, String configurationId) {
+
+      String configurationPrefix = CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + configurationId + ".";
+
+      String language = settings.getString(configurationPrefix + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY);
+      String manDays = settings.getString(configurationPrefix + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_MAN_DAYS_KEY);
+      String metric = settings.getString(configurationPrefix + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_SIZE_METRIC_KEY);
+
+      return new LanguageSpecificConfiguration(language, manDays, metric);
+    }
+
+    String getLanguage() {
+      return language;
+    }
+
+    String getManDays() {
+      return manDays;
+    }
+
+    String getMetric() {
+      return metric;
+    }
+  }
+}
index 0935b576062661809f961fcd1877d823264086c9..455567e5e680326de6c5fa212e7fdf510792acc0 100644 (file)
@@ -28,20 +28,13 @@ import org.sonar.api.batch.rule.CheckFactory;
 import org.sonar.api.platform.ComponentContainer;
 import org.sonar.api.resources.Project;
 import org.sonar.api.scan.filesystem.FileExclusions;
-import org.sonar.batch.DefaultProjectClasspath;
-import org.sonar.batch.DefaultSensorContext;
-import org.sonar.batch.DefaultTimeMachine;
-import org.sonar.batch.ProjectTree;
-import org.sonar.batch.ResourceFilters;
-import org.sonar.batch.ViolationFilters;
+import org.sonar.batch.*;
 import org.sonar.batch.bootstrap.BatchExtensionDictionnary;
 import org.sonar.batch.bootstrap.ExtensionInstaller;
 import org.sonar.batch.bootstrap.ExtensionMatcher;
 import org.sonar.batch.bootstrap.ExtensionUtils;
 import org.sonar.batch.components.TimeMachineConfiguration;
-import org.sonar.batch.debt.DebtDecorator;
-import org.sonar.batch.debt.IssueChangelogDebtCalculator;
-import org.sonar.batch.debt.NewDebtDecorator;
+import org.sonar.batch.debt.*;
 import org.sonar.batch.events.EventBus;
 import org.sonar.batch.index.DefaultIndex;
 import org.sonar.batch.index.ResourcePersister;
@@ -60,26 +53,8 @@ import org.sonar.batch.phases.PhasesTimeProfiler;
 import org.sonar.batch.qualitygate.GenerateQualityGateEvents;
 import org.sonar.batch.qualitygate.QualityGateProvider;
 import org.sonar.batch.qualitygate.QualityGateVerifier;
-import org.sonar.batch.rule.ActiveRulesProvider;
-import org.sonar.batch.rule.ModuleQProfiles;
-import org.sonar.batch.rule.QProfileDecorator;
-import org.sonar.batch.rule.QProfileEventsDecorator;
-import org.sonar.batch.rule.QProfileSensor;
-import org.sonar.batch.rule.QProfileVerifier;
-import org.sonar.batch.rule.RulesProfileProvider;
-import org.sonar.batch.scan.filesystem.ComponentIndexer;
-import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem;
-import org.sonar.batch.scan.filesystem.DeprecatedFileFilters;
-import org.sonar.batch.scan.filesystem.ExclusionFilters;
-import org.sonar.batch.scan.filesystem.FileIndexer;
-import org.sonar.batch.scan.filesystem.FileSystemLogger;
-import org.sonar.batch.scan.filesystem.InputFileBuilderFactory;
-import org.sonar.batch.scan.filesystem.LanguageDetectionFactory;
-import org.sonar.batch.scan.filesystem.ModuleFileSystemInitializer;
-import org.sonar.batch.scan.filesystem.ModuleInputFileCache;
-import org.sonar.batch.scan.filesystem.PreviousFileHashLoader;
-import org.sonar.batch.scan.filesystem.ProjectFileSystemAdapter;
-import org.sonar.batch.scan.filesystem.StatusDetectionFactory;
+import org.sonar.batch.rule.*;
+import org.sonar.batch.scan.filesystem.*;
 import org.sonar.batch.scan.report.JsonReport;
 import org.sonar.batch.scan2.AnalyzerOptimizer;
 import org.sonar.core.component.ScanPerspectives;
@@ -189,6 +164,8 @@ public class ModuleScanContainer extends ComponentContainer {
       IssueChangelogDebtCalculator.class,
       DebtDecorator.class,
       NewDebtDecorator.class,
+      SqaleRatingDecorator.class,
+      SqaleRatingSettings.class,
 
       ScanPerspectives.class);
   }
diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingDecoratorTest.java
new file mode 100644 (file)
index 0000000..1fe1574
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * 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.batch.debt;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.fs.internal.DefaultFileSystem;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.config.Settings;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.test.IsMeasure;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SqaleRatingDecoratorTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  static final Long ONE_DAY_IN_MINUTES = 8L * 60;
+
+  Settings settings;
+  Metric[] metrics = {CoreMetrics.NCLOC, CoreMetrics.ABSTRACTNESS, CoreMetrics.COMPLEXITY};
+
+  @Mock
+  DecoratorContext context;
+
+  DefaultFileSystem fs;
+
+  File file = File.create("src/main/java/Foo.java");
+
+  SqaleRatingDecorator decorator;
+
+  @Before
+  public void setUp() throws Exception {
+    settings = new Settings();
+
+    fs = new DefaultFileSystem();
+    fs.add(new DefaultInputFile(file.getPath())
+      .setLanguage("java")
+      .setFile(temp.newFile("Foo.java")));
+
+    decorator = new SqaleRatingDecorator(new SqaleRatingSettings(settings), metrics, fs);
+  }
+
+  @Test
+  public void generates_metrics() throws Exception {
+    SqaleRatingDecorator decorator = new SqaleRatingDecorator();
+    assertThat(decorator.generatesMetrics()).hasSize(2);
+  }
+
+  @Test
+  public void depends_on_metrics() {
+    SqaleRatingDecorator decorator = new SqaleRatingDecorator();
+    assertThat(decorator.dependsOnMetrics()).containsOnly(CoreMetrics.TECHNICAL_DEBT, CoreMetrics.NCLOC, CoreMetrics.COMPLEXITY);
+  }
+
+  @Test
+  public void execute_on_project() throws Exception {
+    SqaleRatingDecorator decorator = new SqaleRatingDecorator();
+    assertThat(decorator.shouldExecuteOnProject(null)).isTrue();
+  }
+
+  @Test
+  public void not_execute_on_unit_test() throws Exception {
+    File resource = mock(File.class);
+    when(resource.getQualifier()).thenReturn(Qualifiers.UNIT_TEST_FILE);
+    DecoratorContext context = mock(DecoratorContext.class);
+
+    SqaleRatingDecorator decorator = new SqaleRatingDecorator();
+    decorator.decorate(resource, context);
+
+    verify(context, never()).saveMeasure(any(Measure.class));
+  }
+
+  @Test
+  public void save_total_rating_c() {
+    settings.setProperty(CoreProperties.MAN_DAYS_BY_SIZE_POINT, 2 * ONE_DAY_IN_MINUTES);
+    settings.setProperty(CoreProperties.SIZE_METRIC, "ncloc");
+    settings.setProperty(CoreProperties.RATING_GRID, "1, 10,20,50");
+
+    when(context.getResource()).thenReturn(file);
+    when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 10.0));
+    when(context.getMeasure(CoreMetrics.TECHNICAL_DEBT)).thenReturn(new Measure(CoreMetrics.TECHNICAL_DEBT, 300.0 * ONE_DAY_IN_MINUTES));
+
+    decorator.decorate(file, context);
+    verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.RATING, 3.0)));
+    verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.DEVELOPMENT_COST, "9600")));
+
+    verify(context).getMeasure(CoreMetrics.NCLOC);
+  }
+
+  @Test
+  public void save_total_rating_a() {
+    settings.setProperty(CoreProperties.MAN_DAYS_BY_SIZE_POINT, 2 * ONE_DAY_IN_MINUTES);
+    settings.setProperty(CoreProperties.SIZE_METRIC, "ncloc");
+    settings.setProperty(CoreProperties.RATING_GRID, "1, 10,20,50");
+
+    when(context.getResource()).thenReturn(file);
+    when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 10.0));
+    when(context.getMeasure(CoreMetrics.TECHNICAL_DEBT)).thenReturn(new Measure(CoreMetrics.TECHNICAL_DEBT, 0.0));
+
+    decorator.decorate(file, context);
+    verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.RATING, 1.0)));
+    verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.DEVELOPMENT_COST, "9600")));
+
+    verify(context).getMeasure(CoreMetrics.NCLOC);
+  }
+
+  @Test
+  public void save_total_rating_e() {
+    settings.setProperty(CoreProperties.MAN_DAYS_BY_SIZE_POINT, 2 * ONE_DAY_IN_MINUTES);
+    settings.setProperty(CoreProperties.SIZE_METRIC, "ncloc");
+    settings.setProperty(CoreProperties.RATING_GRID, "1, 10,20,50");
+
+    when(context.getResource()).thenReturn(file);
+    when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 10.0));
+    when(context.getMeasure(CoreMetrics.TECHNICAL_DEBT)).thenReturn(new Measure(CoreMetrics.TECHNICAL_DEBT, 100000000000.0));
+
+    decorator.decorate(file, context);
+    verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.RATING, 5.0)));
+    verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.DEVELOPMENT_COST, "9600")));
+
+    verify(context).getMeasure(CoreMetrics.NCLOC);
+  }
+
+  @Test
+  public void save_total_rating_on_project() {
+    settings.setProperty(CoreProperties.RATING_GRID, "1, 10,20,50");
+
+    when(context.getResource()).thenReturn(new Project("Sample"));
+    when(context.getMeasure(CoreMetrics.TECHNICAL_DEBT)).thenReturn(new Measure(CoreMetrics.TECHNICAL_DEBT, 300.0 * ONE_DAY_IN_MINUTES));
+    when(context.getChildrenMeasures(CoreMetrics.DEVELOPMENT_COST)).thenReturn(newArrayList(new Measure(CoreMetrics.DEVELOPMENT_COST, Double.toString(20.0 * ONE_DAY_IN_MINUTES))));
+
+    decorator.decorate(mock(File.class), context);
+    verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.RATING, 3.0)));
+    verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.DEVELOPMENT_COST, "9600")));
+
+    verify(context, never()).getMeasure(CoreMetrics.NCLOC);
+  }
+
+  @Test
+  public void translate_rating_to_letter() {
+    assertThat(SqaleRatingDecorator.toRatingLetter(null)).isNull();
+    assertThat(SqaleRatingDecorator.toRatingLetter(1)).isEqualTo("A");
+    assertThat(SqaleRatingDecorator.toRatingLetter(4)).isEqualTo("D");
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void test_rating_out_of_range() {
+    SqaleRatingDecorator.toRatingLetter(89);
+  }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingGridTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingGridTest.java
new file mode 100644 (file)
index 0000000..cabf747
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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.batch.debt;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class SqaleRatingGridTest {
+
+  private SqaleRatingGrid ratingGrid;
+
+  @Rule
+  public ExpectedException throwable = ExpectedException.none();
+
+  @Before
+  public void setUp() {
+    double[] gridValues = new double[] {0.1, 0.2, 0.5, 1};
+    ratingGrid = new SqaleRatingGrid(gridValues);
+  }
+
+  @Test
+  public void return_grade_lower_bound() throws Exception {
+    assertThat(ratingGrid.getGradeLowerBound(SqaleRatingGrid.SqaleRating.A)).isEqualTo(0);
+    assertThat(ratingGrid.getGradeLowerBound(SqaleRatingGrid.SqaleRating.B)).isEqualTo(0.1);
+    assertThat(ratingGrid.getGradeLowerBound(SqaleRatingGrid.SqaleRating.C)).isEqualTo(0.2);
+    assertThat(ratingGrid.getGradeLowerBound(SqaleRatingGrid.SqaleRating.D)).isEqualTo(0.5);
+    assertThat(ratingGrid.getGradeLowerBound(SqaleRatingGrid.SqaleRating.E)).isEqualTo(1);
+  }
+
+  @Test
+  public void return_rating_matching_density() throws Exception {
+    assertThat(ratingGrid.getRatingForDensity(0)).isEqualTo(1);
+    assertThat(ratingGrid.getRatingForDensity(0.05)).isEqualTo(1);
+    assertThat(ratingGrid.getRatingForDensity(0.1)).isEqualTo(2);
+    assertThat(ratingGrid.getRatingForDensity(0.15)).isEqualTo(2);
+    assertThat(ratingGrid.getRatingForDensity(0.2)).isEqualTo(3);
+    assertThat(ratingGrid.getRatingForDensity(0.25)).isEqualTo(3);
+    assertThat(ratingGrid.getRatingForDensity(0.5)).isEqualTo(4);
+    assertThat(ratingGrid.getRatingForDensity(0.65)).isEqualTo(4);
+    assertThat(ratingGrid.getRatingForDensity(1)).isEqualTo(5);
+    assertThat(ratingGrid.getRatingForDensity(1.01)).isEqualTo(5);
+  }
+
+  @Test
+  public void fail_on_invalid_density() throws Exception {
+    throwable.expect(RuntimeException.class);
+
+    ratingGrid.getRatingForDensity(-1);
+  }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingSettingsTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/SqaleRatingSettingsTest.java
new file mode 100644 (file)
index 0000000..cd784f1
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * 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.batch.debt;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+
+public class SqaleRatingSettingsTest {
+
+  private static final Metric[] metrics = {CoreMetrics.NCLOC, CoreMetrics.ABSTRACTNESS, CoreMetrics.COMPLEXITY};
+
+  private Settings settings;
+
+  @Rule
+  public ExpectedException throwable = ExpectedException.none();
+
+  @Before
+  public void setUp() {
+    settings = new Settings();
+  }
+
+  @Test
+  public void load_rating_grid() throws Exception {
+    settings.setProperty(CoreProperties.RATING_GRID, "1,3.4,8,50");
+    SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+    double[] grid = configurationLoader.getRatingGrid();
+    assertThat(grid).hasSize(4);
+    assertThat(grid[0]).isEqualTo(1.0);
+    assertThat(grid[1]).isEqualTo(3.4);
+    assertThat(grid[2]).isEqualTo(8.0);
+    assertThat(grid[3]).isEqualTo(50.0);
+  }
+
+  @Test
+  public void load_work_units_for_language() throws Exception {
+    settings.setProperty(CoreProperties.MAN_DAYS_BY_SIZE_POINT, "50");
+    SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+    assertThat(configurationLoader.getWorkUnitsBySizePoint("defaultLanguage")).isEqualTo(50L);
+  }
+
+  @Test
+  public void load_size_metric_for_language() throws Exception {
+    settings.setProperty(CoreProperties.SIZE_METRIC, "complexity");
+    SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+    assertThat(configurationLoader.getSizeMetric("defaultLanguage", metrics)).isEqualTo(CoreMetrics.COMPLEXITY);
+  }
+
+  @Test
+  public void load_overridden_values_for_language() throws Exception {
+
+    String aLanguage = "aLanguage";
+    String anotherLanguage = "anotherLanguage";
+
+    settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS, "0,1");
+    settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + "0" + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY, aLanguage);
+    settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + "0" + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_MAN_DAYS_KEY, "30");
+    settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + "0" + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_SIZE_METRIC_KEY, CoreMetrics.NCLOC_KEY);
+    settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + "1" + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY, anotherLanguage);
+    settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + "1" + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_MAN_DAYS_KEY, "40");
+    settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + "1" + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_SIZE_METRIC_KEY, CoreMetrics.COMPLEXITY_KEY);
+
+    SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+    assertThat(configurationLoader.getSizeMetric(aLanguage, metrics)).isEqualTo(CoreMetrics.NCLOC);
+    assertThat(configurationLoader.getSizeMetric(anotherLanguage, metrics)).isEqualTo(CoreMetrics.COMPLEXITY);
+    assertThat(configurationLoader.getWorkUnitsBySizePoint(aLanguage)).isEqualTo(30L);
+    assertThat(configurationLoader.getWorkUnitsBySizePoint(anotherLanguage)).isEqualTo(40L);
+  }
+
+  @Test
+  public void fail_on_invalid_rating_grid_configuration() throws Exception {
+
+    throwable.expect(IllegalArgumentException.class);
+    settings.setProperty(CoreProperties.RATING_GRID, "a b c");
+    SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+    configurationLoader.getRatingGrid();
+  }
+  
+  @Test
+  public void fail_on_invalid_work_unit_value() throws Exception {
+    throwable.expect(IllegalArgumentException.class);
+    settings.setProperty(CoreProperties.MAN_DAYS_BY_SIZE_POINT, "a");
+    SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+    configurationLoader.getSizeMetric("aLanguage", metrics);
+  }
+
+  @Test
+  public void fail_on_unknown_metric_key() throws Exception {
+    throwable.expect(IllegalArgumentException.class);
+    settings.setProperty(CoreProperties.SIZE_METRIC, "unknown");
+    SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+    configurationLoader.getSizeMetric("aLanguage", metrics);
+  }
+
+  @Test
+  public void use_generic_value_when_specific_setting_is_missing() throws Exception {
+    String aLanguage = "aLanguage";
+
+    settings.setProperty(CoreProperties.SIZE_METRIC, "complexity");
+    settings.setProperty(CoreProperties.MAN_DAYS_BY_SIZE_POINT, "30");
+    settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS, "0");
+    settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + "0" + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY, aLanguage);
+    settings.setProperty(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS + "." + "0" + "." + CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_MAN_DAYS_KEY, "40");
+
+    SqaleRatingSettings configurationLoader = new SqaleRatingSettings(settings);
+
+    assertThat(configurationLoader.getSizeMetric(aLanguage, metrics)).isEqualTo(CoreMetrics.COMPLEXITY);
+    assertThat(configurationLoader.getWorkUnitsBySizePoint(aLanguage)).isEqualTo(40L);
+  }
+}
index 82bb38e79b61b95734fba9fab38b6e2579d53632..d98fddab931190d4b76168056106d59ef44364ce 100644 (file)
@@ -22,7 +22,6 @@ package org.sonar.core.config;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import org.sonar.api.CoreProperties;
-import org.sonar.api.PropertyType;
 import org.sonar.api.config.PropertyDefinition;
 import org.sonar.api.resources.Qualifiers;
 
@@ -39,18 +38,9 @@ public class CorePropertyDefinitions {
     defs.addAll(IssueExclusionProperties.all());
     defs.addAll(ExclusionProperties.all());
     defs.addAll(SecurityProperties.all());
+    defs.addAll(DebtProperties.all());
 
     defs.addAll(ImmutableList.of(
-
-      // DEBT
-      PropertyDefinition.builder(CoreProperties.HOURS_IN_DAY)
-        .name("Number of working hours in a day")
-        .type(PropertyType.INTEGER)
-        .defaultValue("8")
-        .category(CoreProperties.CATEGORY_TECHNICAL_DEBT)
-        .deprecatedKey("sqale.hoursInDay")
-        .build(),
-
       // BATCH
 
       PropertyDefinition.builder(CoreProperties.CORE_VIOLATION_LOCALE_PROPERTY)
@@ -111,7 +101,7 @@ public class CorePropertyDefinitions {
         .category(CoreProperties.CATEGORY_GENERAL)
         .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS)
         .build()
-    ));
+      ));
     return defs;
   }
 }
diff --git a/sonar-core/src/main/java/org/sonar/core/config/DebtProperties.java b/sonar-core/src/main/java/org/sonar/core/config/DebtProperties.java
new file mode 100644 (file)
index 0000000..8979927
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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.core.config;
+
+import com.google.common.collect.ImmutableList;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.PropertyType;
+import org.sonar.api.config.PropertyDefinition;
+import org.sonar.api.config.PropertyFieldDefinition;
+import org.sonar.api.measures.CoreMetrics;
+
+import java.util.List;
+
+class DebtProperties {
+
+  private DebtProperties() {
+    // only static stuff
+  }
+
+  static List<PropertyDefinition> all() {
+    return ImmutableList.of(
+      PropertyDefinition.builder(CoreProperties.HOURS_IN_DAY)
+        .name("Number of working hours in a day")
+        .type(PropertyType.INTEGER)
+        .defaultValue("8")
+        .category(CoreProperties.CATEGORY_TECHNICAL_DEBT)
+        .deprecatedKey("sqale.hoursInDay")
+        .build(),
+
+      PropertyDefinition.builder(CoreProperties.SIZE_METRIC)
+        .defaultValue("" + CoreMetrics.NCLOC_KEY)
+        .name("Size metric")
+        .description("Metric used to estimate artifact's development cost.")
+        .type(PropertyType.METRIC)
+        .options("key:^(ncloc|complexity)$")
+        .category(CoreProperties.CATEGORY_TECHNICAL_DEBT)
+        .deprecatedKey("sizeMetric")
+        .build(),
+
+      PropertyDefinition.builder(CoreProperties.MAN_DAYS_BY_SIZE_POINT)
+        .defaultValue("" + CoreProperties.MAN_DAYS_BY_SIZE_POINT_DEF_VALUE)
+        .name("Development cost")
+        .description("Cost to develop one unit of code. If the unit is a line of code (LOC), and the cost to develop 1 LOC has been estimated at 30 minutes, " +
+          "then the value of this property would be 30.")
+        .category(CoreProperties.CATEGORY_TECHNICAL_DEBT)
+        .deprecatedKey("workUnitsBySizePoint")
+        .build(),
+
+      PropertyDefinition.builder(CoreProperties.RATING_GRID)
+        .defaultValue("" + CoreProperties.RATING_GRID_DEF_VALUES)
+        .name("Rating grid")
+        .description("SQALE ratings range from A (very good) to E (very bad). They compare the technical debt on a project to the time that has already gone into writing it. " +
+          "The default values for A through D are 0.1,0.2,0.5,1. Anything over 1 is an E. " +
+          "Assuming the size metric is lines of code (LOC), and the work unit is 30 (minutes to produce 1 LOC), " +
+          "a project with a technical debt of 24,000 minutes for 2,500 LOC will have a ratio of 24000/(30 * 2,500) = 0.32. That yields a SQALE rating of C.")
+        .category(CoreProperties.CATEGORY_TECHNICAL_DEBT)
+        .deprecatedKey("ratingGrid")
+        .build(),
+
+      PropertyDefinition.builder(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS)
+        .name("Language specific parameters")
+        .description("The parameters specified here for a given language will override the general parameters defined in this section.")
+        .category(CoreProperties.CATEGORY_TECHNICAL_DEBT)
+        .deprecatedKey("languageSpecificParameters")
+        .fields(
+          PropertyFieldDefinition.build(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY)
+            .name("Language Key")
+            .description("Ex: java, cs, cpp...")
+            .type(PropertyType.STRING)
+            .build(),
+          PropertyFieldDefinition.build(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_MAN_DAYS_KEY)
+            .name("Development cost")
+            .description("If left blank, the generic value defined in this section will be used.")
+            .type(PropertyType.FLOAT)
+            .build(),
+          PropertyFieldDefinition.build(CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_SIZE_METRIC_KEY)
+            .name("Size metric")
+            .description("If left blank, the generic value defined in this section will be used.")
+            .type(PropertyType.METRIC)
+            .options("key:^(ncloc|complexity)$")
+            .build()
+        )
+        .build()
+    );
+  }
+}
index 19d79438e44242c481da12f322478ce3f7d8c9a2..ec0bbba4ae5da782769ccfe2634246437237726d 100644 (file)
@@ -2510,9 +2510,13 @@ metric.confirmed_issues.description=Confirmed issues
 
 metric.sqale_index.name=Technical Debt
 metric.sqale_index.description=Total effort (in days) to fix all the issues on the component and therefore to comply to all the requirements.
+
 metric.new_technical_debt.name=Technical Debt on new code
 metric.new_technical_debt.description=Technical Debt on new code
 
+metric.sqale_rating.name=SQALE Rating
+metric.sqale_rating.description=Density of technical debt computed by dividing the technical debt by the estimated effort to develop from scratch an application.
+
 
 #------------------------------------------------------------------------------
 #
index 81765da230e9bcd3e457d8d687e49a386db2e161..ef86086c8be2d4e637b8b69bc3c807b243956b5f 100644 (file)
@@ -567,4 +567,49 @@ public interface CoreProperties {
    * @since 4.0
    */
   String HOURS_IN_DAY = "sonar.technicalDebt.hoursInDay";
+
+  /**
+   * @since 4.5
+   */
+  String SIZE_METRIC = "sonar.technicalDebt.sizeMetric";
+
+  /**
+   * @since 4.5
+   */
+  String MAN_DAYS_BY_SIZE_POINT = "sonar.technicalDebt.workUnitsBySizePoint";
+
+  /**
+   * @since 4.5
+   */
+  String MAN_DAYS_BY_SIZE_POINT_DEF_VALUE = "30";
+
+  /**
+   * @since 4.5
+   */
+  String RATING_GRID = "sonar.technicalDebt.ratingGrid";
+
+  /**
+   * @since 4.5
+   */
+  String RATING_GRID_DEF_VALUES = "0.1,0.2,0.5,1";
+
+  /**
+   * @since 4.5
+   */
+  String LANGUAGE_SPECIFIC_PARAMETERS = "languageSpecificParameters";
+
+  /**
+   * @since 4.5
+   */
+  String LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY = "language";
+
+  /**
+   * @since 4.5
+   */
+  String LANGUAGE_SPECIFIC_PARAMETERS_MAN_DAYS_KEY = "man_days";
+
+  /**
+   * @since 4.5
+   */
+  String LANGUAGE_SPECIFIC_PARAMETERS_SIZE_METRIC_KEY = "size_metric";
 }
index 5051dd5e2e14bdc0799f0d95a54528cbf9833cc2..c0d57a8972845f60fef685689f7ddc78641258db 100644 (file)
@@ -2122,6 +2122,39 @@ public final class CoreMetrics {
     .setDeleteHistoricalData(true)
     .create();
 
+  /**
+   * @since 4.5
+   */
+  public static final String SQALE_RATING_KEY = "sqale_rating";
+
+  /**
+   * @since 4.5
+   */
+  public static final Metric<String> RATING = new Metric.Builder(SQALE_RATING_KEY, "SQALE Rating", Metric.ValueType.RATING)
+    .setDomain(DOMAIN_TECHNICAL_DEBT)
+    .setDirection(Metric.DIRECTION_WORST)
+    .setQualitative(true)
+    .setBestValue(1.0)
+    .setWorstValue(5.0)
+    .create();
+
+  /**
+   * @since 4.5
+   */
+  public static final String DEVELOPMENT_COST_KEY = "development_cost";
+
+  /**
+   * @since 4.5
+   */
+  public static final Metric<String> DEVELOPMENT_COST = new Metric.Builder(DEVELOPMENT_COST_KEY, "SQALE Development Cost", Metric.ValueType.STRING)
+    .setDomain(DOMAIN_TECHNICAL_DEBT)
+    .setDirection(Metric.DIRECTION_WORST)
+    .setOptimizedBestValue(true)
+    .setBestValue(0.0)
+    .setQualitative(true)
+    .setHidden(true)
+    .create();
+
   // --------------------------------------------------------------------------------------------------------------------
   //
   // FILE DATA
index 1db080ea3c37869f9f23b74aa868d53285587b72..086005be0f83aa8101c1bee2f8d094567a294645 100644 (file)
@@ -32,7 +32,7 @@ public class CoreMetricsTest {
   @Test
   public void read_metrics_from_class_reflection() {
     List<Metric> metrics = CoreMetrics.getMetrics();
-    assertThat(metrics).hasSize(150);
+    assertThat(metrics).hasSize(152);
     assertThat(metrics).contains(CoreMetrics.NCLOC, CoreMetrics.DIRECTORIES);
   }