]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4715 Compute the new "Technical Debt" metric
authorJulien Lancelot <julien.lancelot@gmail.com>
Fri, 27 Sep 2013 12:06:47 +0000 (14:06 +0200)
committerJulien Lancelot <julien.lancelot@gmail.com>
Fri, 27 Sep 2013 12:06:58 +0000 (14:06 +0200)
32 files changed:
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Characteristic.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Characteristicable.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Requirement.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculator.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtModel.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/WorkUnit.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/WorkUnitConverter.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/AbstractFunction.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/ConstantFunction.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/Function.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/Functions.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearFunction.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithOffsetFunction.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithThresholdFunction.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/RequirementTest.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculatorTest.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtModelTest.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/WorkUnitConverterTest.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/ConstantFunctionTest.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/FunctionsTest.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearFunctionTest.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithOffsetFunctionTest.java [new file with mode: 0644]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithThresholdFunctionTest.java [new file with mode: 0644]
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
sonar-server/src/main/java/org/sonar/server/technicaldebt/XMLConstants.java [deleted file]
sonar-server/src/main/java/org/sonar/server/technicaldebt/XMLImporter.java

index e672a9d846ce3019c8a3074d28126eca3bf032eb..d0315062434bd2b63ca165da09648b6d8c458819 100644 (file)
@@ -41,6 +41,7 @@ import org.sonar.plugins.core.measurefilters.ProjectFilter;
 import org.sonar.plugins.core.notifications.alerts.NewAlerts;
 import org.sonar.plugins.core.security.ApplyProjectRolesDecorator;
 import org.sonar.plugins.core.sensors.*;
+import org.sonar.plugins.core.technicaldebt.TechnicalDebtDecorator;
 import org.sonar.plugins.core.timemachine.*;
 import org.sonar.plugins.core.web.Lcom4Viewer;
 import org.sonar.plugins.core.web.TestsViewer;
@@ -326,6 +327,7 @@ public final class CorePlugin extends SonarPlugin {
     extensions.addAll(IgnoreIssuesPlugin.getExtensions());
     extensions.addAll(CoverageMeasurementFilter.getPropertyDefinitions());
     extensions.addAll(PastSnapshotFinder.getPropertyDefinitions());
+    extensions.addAll(TechnicalDebtDecorator.extensions());
     extensions.addAll(propertyDefinitions());
 
     return extensions.build();
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Characteristic.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Characteristic.java
new file mode 100644 (file)
index 0000000..a3b7cbf
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt;
+
+import com.google.common.collect.Lists;
+
+import javax.annotation.Nullable;
+
+import java.util.List;
+
+public final class Characteristic implements Characteristicable {
+
+  private String key;
+  private org.sonar.api.qualitymodel.Characteristic characteristic;
+  private Characteristic parent = null;
+  private List<Characteristic> subCharacteristics = Lists.newArrayList();
+  private List<Requirement> requirements = Lists.newArrayList();
+
+  public Characteristic(org.sonar.api.qualitymodel.Characteristic c) {
+    this(c, null);
+  }
+
+  public Characteristic(org.sonar.api.qualitymodel.Characteristic c, @Nullable Characteristic parent) {
+    this.characteristic = c;
+    this.key = c.getKey();
+    this.parent = parent;
+    for (org.sonar.api.qualitymodel.Characteristic subc : c.getChildren()) {
+      if (subc.getEnabled()) {
+        if (subc.getRule() != null) {
+          requirements.add(new Requirement(subc, this));
+        } else {
+          subCharacteristics.add(new Characteristic(subc, this));
+        }
+      }
+    }
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  public List<Characteristic> getSubCharacteristics() {
+    return subCharacteristics;
+  }
+
+  public Characteristic getParent() {
+    return parent;
+  }
+
+  public List<Requirement> getRequirements() {
+    return requirements;
+  }
+
+  public org.sonar.api.qualitymodel.Characteristic toCharacteristic() {
+    return characteristic;
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Characteristicable.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Characteristicable.java
new file mode 100644 (file)
index 0000000..5beeb72
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt;
+
+import org.sonar.api.qualitymodel.Characteristic;
+
+public interface Characteristicable {
+
+  Characteristic toCharacteristic();
+  
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Requirement.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Requirement.java
new file mode 100644 (file)
index 0000000..c10dc52
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt;
+
+import org.sonar.api.rules.Rule;
+import org.sonar.plugins.core.technicaldebt.functions.LinearFunction;
+import org.sonar.plugins.core.technicaldebt.functions.LinearWithOffsetFunction;
+import org.sonar.plugins.core.technicaldebt.functions.LinearWithThresholdFunction;
+
+public class Requirement implements Characteristicable {
+
+  public static final String PROPERTY_REMEDIATION_FUNCTION = "remediationFunction";
+  public static final String PROPERTY_REMEDIATION_FACTOR = "remediationFactor";
+  public static final String PROPERTY_OFFSET = "offset";
+
+  private Rule rule;
+  private Characteristic parent;
+  private org.sonar.api.qualitymodel.Characteristic characteristic;
+  private String function;
+  private WorkUnit factor;
+  private WorkUnit offset;
+
+  public Requirement(org.sonar.api.qualitymodel.Characteristic requirement, Characteristic parent) {
+    this.characteristic = requirement;
+    this.rule = requirement.getRule();
+    this.parent = parent;
+
+    initFunction();
+    initFactor();
+    initOffset();
+  }
+
+  private void initFunction() {
+    function = characteristic.getPropertyTextValue(PROPERTY_REMEDIATION_FUNCTION, LinearFunction.FUNCTION_LINEAR);
+  }
+
+  private void initFactor() {
+    factor = WorkUnit.create(characteristic.getPropertyValue(PROPERTY_REMEDIATION_FACTOR, null),
+        characteristic.getPropertyTextValue(PROPERTY_REMEDIATION_FACTOR, null));
+  }
+
+  private void initOffset() {
+    if (LinearWithOffsetFunction.FUNCTION_LINEAR_WITH_OFFSET.equals(function) || LinearWithThresholdFunction.FUNCTION_LINEAR_WITH_THRESHOLD.equals(function)) {
+      offset = WorkUnit.create(characteristic.getPropertyValue(PROPERTY_OFFSET, null),
+          characteristic.getPropertyTextValue(PROPERTY_OFFSET, null));
+    }
+  }
+
+  public Rule getRule() {
+    return rule;
+  }
+
+  public Characteristic getParent() {
+    return parent;
+  }
+
+  public String getRemediationFunction() {
+    return function;
+  }
+
+  public WorkUnit getRemediationFactor() {
+    return factor;
+  }
+
+  public WorkUnit getOffset() {
+    return offset;
+  }
+
+  public org.sonar.api.qualitymodel.Characteristic toCharacteristic() {
+    return characteristic;
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculator.java
new file mode 100644 (file)
index 0000000..0c4e737
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.BatchExtension;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasuresFilters;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.rules.Violation;
+import org.sonar.plugins.core.technicaldebt.functions.Functions;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Computes the remediation cost based on the quality and analysis models.
+ */
+public class TechnicalDebtCalculator implements BatchExtension {
+
+  private double total = 0.0;
+  private Map<Characteristic, Double> characteristicCosts = Maps.newHashMap();
+  private Map<Requirement, Double> requirementCosts = Maps.newHashMap();
+
+  private Functions functions;
+  private TechnicalDebtModel technicalDebtModel;
+
+  public TechnicalDebtCalculator(TechnicalDebtModel technicalDebtModel, Functions functions) {
+    this.technicalDebtModel = technicalDebtModel;
+    this.functions = functions;
+  }
+
+  public void compute(DecoratorContext context) {
+    reset();
+
+    // group violations by requirement
+    ListMultimap<Requirement, Violation> violationsByRequirement = groupViolations(context);
+
+    // the total cost is: cost(violations)
+    for (Requirement requirement : technicalDebtModel.getAllRequirements()) {
+      List<Violation> violations = violationsByRequirement.get(requirement);
+      double allViolationsCost = computeRemediationCost(CoreMetrics.TECHNICAL_DEBT, context, requirement, violations);
+      updateRequirementCosts(requirement, allViolationsCost);
+    }
+  }
+
+  public double getTotal() {
+    return total;
+  }
+
+  public Map<Characteristic, Double> getCharacteristicCosts() {
+    return characteristicCosts;
+  }
+
+  public Map<Requirement, Double> getRequirementCosts() {
+    return requirementCosts;
+  }
+
+  @VisibleForTesting
+  protected ListMultimap<Requirement, Violation> groupViolations(DecoratorContext context) {
+    ListMultimap<Requirement, Violation> violationsByRequirement = ArrayListMultimap.create();
+    for (Violation violation : context.getViolations()) {
+      String repositoryKey = violation.getRule().getRepositoryKey();
+      String key = violation.getRule().getKey();
+      Requirement requirement = technicalDebtModel.getRequirementByRule(repositoryKey, key);
+      if (requirement == null) {
+        LoggerFactory.getLogger(getClass()).debug("No technical debt requirement for: " + repositoryKey + "/" + key);
+      } else {
+        violationsByRequirement.put(requirement, violation);
+      }
+    }
+    return violationsByRequirement;
+  }
+
+  @VisibleForTesting
+  protected void updateRequirementCosts(Requirement requirement, double cost) {
+    requirementCosts.put(requirement, cost);
+    total += cost;
+    propagateCostInParents(characteristicCosts, requirement.getParent(), cost);
+  }
+
+  private double computeRemediationCost(Metric metric, DecoratorContext context, Requirement requirement, Collection<Violation> violations) {
+    double cost = 0.0;
+    if (violations != null) {
+      cost = functions.calculateCost(requirement, violations);
+    }
+
+    for (Measure measure : context.getChildrenMeasures(MeasuresFilters.characteristic(metric, requirement.toCharacteristic()))) {
+      if (measure.getCharacteristic() != null && measure.getCharacteristic().equals(requirement.toCharacteristic()) && measure.getValue() != null) {
+        cost += measure.getValue();
+      }
+    }
+    return cost;
+  }
+
+  private void reset() {
+    total = 0.0;
+    characteristicCosts.clear();
+    requirementCosts.clear();
+  }
+
+  private void propagateCostInParents(Map<Characteristic, Double> hierarchyMap, Characteristic characteristic, double cost) {
+    if (characteristic != null) {
+      Double parentCost = hierarchyMap.get(characteristic);
+      if (parentCost == null) {
+        hierarchyMap.put(characteristic, cost);
+      } else {
+        hierarchyMap.put(characteristic, cost + parentCost);
+      }
+      propagateCostInParents(hierarchyMap, characteristic.getParent(), cost);
+    }
+  }
+
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java
new file mode 100644 (file)
index 0000000..00d11f4
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt;
+
+import com.google.common.collect.ImmutableList;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.*;
+import org.sonar.api.config.PropertyDefinition;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.PersistenceMode;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+import org.sonar.plugins.core.technicaldebt.functions.*;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Decorator that computes the technical debt metric
+ */
+@DependsUpon(DecoratorBarriers.ISSUES_TRACKED)
+public final class TechnicalDebtDecorator implements Decorator {
+
+  public static final int DECIMALS_PRECISION = 5;
+  private TechnicalDebtCalculator costCalculator;
+
+  public TechnicalDebtDecorator(TechnicalDebtCalculator costCalculator) {
+    this.costCalculator = costCalculator;
+  }
+
+  public boolean shouldExecuteOnProject(Project project) {
+    return true;
+  }
+
+  @DependedUpon
+  public List<Metric> generatesMetrics() {
+    return Arrays.asList(CoreMetrics.TECHNICAL_DEBT);
+  }
+
+  @SuppressWarnings("rawtypes")
+  public void decorate(Resource resource, DecoratorContext context) {
+    if (!ResourceUtils.isUnitTestClass(resource)) {
+      costCalculator.compute(context);
+      saveCostMeasures(context);
+    }
+  }
+
+  protected void saveCostMeasures(DecoratorContext context) {
+    context.saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT, costCalculator.getTotal(), DECIMALS_PRECISION));
+    saveCharacteristicCosts(context);
+    saveRequirementCosts(context);
+  }
+
+  private void saveCharacteristicCosts(DecoratorContext context) {
+    for (Map.Entry<Characteristic, Double> entry : costCalculator.getCharacteristicCosts().entrySet()) {
+      saveCost(context, entry.getKey().toCharacteristic(), entry.getValue(), false);
+    }
+  }
+
+  private void saveRequirementCosts(DecoratorContext context) {
+    for (Map.Entry<Requirement, Double> entry : costCalculator.getRequirementCosts().entrySet()) {
+      saveCost(context, entry.getKey().toCharacteristic(), entry.getValue(), ResourceUtils.isEntity(context.getResource()));
+    }
+  }
+
+  protected void saveCost(DecoratorContext context, org.sonar.api.qualitymodel.Characteristic characteristic, Double value, boolean inMemory) {
+    // we need the value on projects (root or module) even if value==0 in order to display correctly the SQALE history chart (see SQALE-122)
+    // BUT we don't want to save zero-values for non top-characteristics (see SQALE-147)
+    if (value > 0.0 || (ResourceUtils.isProject(context.getResource()) && characteristic.getDepth() == org.sonar.api.qualitymodel.Characteristic.ROOT_DEPTH)) {
+      Measure measure = new Measure(CoreMetrics.TECHNICAL_DEBT);
+      measure.setCharacteristic(characteristic);
+      measure.setValue(value, DECIMALS_PRECISION);
+      if (inMemory) {
+        measure.setPersistenceMode(PersistenceMode.MEMORY);
+      }
+      context.saveMeasure(measure);
+    }
+  }
+
+  public static List<?> extensions() {
+    ImmutableList.Builder<Object> extensions = ImmutableList.builder();
+    extensions.addAll(definitions());
+    extensions.add(
+      // base components
+      TechnicalDebtModel.class, WorkUnitConverter.class, TechnicalDebtCalculator.class,
+
+      // functions
+      ConstantFunction.class, LinearFunction.class, LinearWithOffsetFunction.class, LinearWithThresholdFunction.class, Functions.class,
+
+      // decorator
+      TechnicalDebtDecorator.class
+    );
+
+    return extensions.build();
+  }
+
+  private static List<PropertyDefinition> definitions() {
+    return ImmutableList.of(
+      PropertyDefinition.builder(WorkUnitConverter.PROPERTY_HOURS_IN_DAY)
+        .name("Number of working hours in a day")
+        .defaultValue("" + WorkUnitConverter.DEFAULT_HOURS_IN_DAY)
+        .category(CoreProperties.CATEGORY_TECHNICAL_DEBT)
+        .build()
+    );
+  }
+
+  @Override
+  public String toString() {
+    return "Technical Debt decorator";
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtModel.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtModel.java
new file mode 100644 (file)
index 0000000..b5a969d
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.sonar.api.BatchExtension;
+import org.sonar.api.qualitymodel.Model;
+import org.sonar.api.qualitymodel.ModelFinder;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.utils.SonarException;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+public class TechnicalDebtModel implements BatchExtension {
+
+  // FIXME Use the same as in RegisterTechnicalDebtModel
+  public static final String MODEL_NAME = "TECHNICAL_DEBT";
+
+  private List<Characteristic> characteristics = Lists.newArrayList();
+  private Map<Rule, Requirement> requirementsByRule = Maps.newHashMap();
+
+  public TechnicalDebtModel(ModelFinder modelFinder) {
+    Model model = modelFinder.findByName(MODEL_NAME);
+    if (model == null) {
+      throw new SonarException("Can not find the model in database: " + MODEL_NAME);
+    }
+    init(model);
+  }
+
+  /**
+   * For unit tests
+   */
+  private TechnicalDebtModel(Model model) {
+    init(model);
+  }
+
+  /**
+   * For unit tests
+   */
+  public static TechnicalDebtModel create(Model model) {
+    return new TechnicalDebtModel(model);
+  }
+
+  private void init(Model model) {
+    for (org.sonar.api.qualitymodel.Characteristic characteristic : model.getRootCharacteristics()) {
+      if (characteristic.getEnabled()) {
+        Characteristic sc = new Characteristic(characteristic);
+        characteristics.add(sc);
+        registerRequirements(sc);
+      }
+    }
+  }
+
+  private void registerRequirements(Characteristic c) {
+    for (Requirement requirement : c.getRequirements()) {
+
+      if (requirement.getRule() != null) {
+        requirementsByRule.put(requirement.getRule(), requirement);
+      }
+    }
+    for (Characteristic subCharacteristic : c.getSubCharacteristics()) {
+      registerRequirements(subCharacteristic);
+    }
+  }
+
+  public List<Characteristic> getCharacteristics() {
+    return characteristics;
+  }
+
+  public Collection<Requirement> getAllRequirements() {
+    return requirementsByRule.values();
+  }
+
+  public Requirement getRequirementByRule(String repositoryKey, String key) {
+    return requirementsByRule.get(Rule.create().setUniqueKey(repositoryKey, key));
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/WorkUnit.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/WorkUnit.java
new file mode 100644 (file)
index 0000000..f5d20db
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang.StringUtils;
+
+import javax.annotation.Nullable;
+
+public final class WorkUnit {
+
+  public static final String DAYS = "d";
+  public static final String MINUTES = "mn";
+  public static final String HOURS = "h";
+  public static final String DEFAULT_UNIT = DAYS;
+  private static final String[] UNITS = {DAYS, MINUTES, HOURS};
+
+  public static final double DEFAULT_VALUE = 1.0;
+
+  private double value;
+  private String unit;
+
+  WorkUnit(double value, String unit) {
+    this.value = value;
+    this.unit = unit;
+  }
+
+  public double getValue() {
+    return value;
+  }
+
+  public String getUnit() {
+    return unit;
+  }
+
+  public static WorkUnit create(@Nullable Double value, @Nullable String unit) {
+    unit = StringUtils.defaultIfEmpty(unit, DEFAULT_UNIT);
+    if (!ArrayUtils.contains(UNITS, unit)) {
+      throw new IllegalArgumentException("Remediation factor unit can not be: " + unit + ". Possible values are " + ArrayUtils.toString(UNITS));
+    }
+    double d = value != null ? value : DEFAULT_VALUE;
+    if (d < 0.0) {
+      throw new IllegalArgumentException("Remediation factor can not be negative: " + d);
+    }
+    return new WorkUnit(d, unit);
+  }
+
+  public static WorkUnit createInDays(Double value) {
+    return create(value, DAYS);
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/WorkUnitConverter.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/WorkUnitConverter.java
new file mode 100644 (file)
index 0000000..0255f16
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.BatchExtension;
+
+public final class WorkUnitConverter implements BatchExtension {
+
+  public static final int DEFAULT_HOURS_IN_DAY = 8;
+
+  public static final String PROPERTY_HOURS_IN_DAY = "sonar.technicalDebt.hoursInDay";
+
+  private int hoursInDay = DEFAULT_HOURS_IN_DAY;
+
+  public WorkUnitConverter(Configuration configuration) {
+    this.hoursInDay = configuration.getInt(PROPERTY_HOURS_IN_DAY, DEFAULT_HOURS_IN_DAY);
+  }
+
+  public int getHoursInDay() {
+    return hoursInDay;
+  }
+
+  public double toDays(WorkUnit factor) {
+    double result;
+    if (StringUtils.equals(WorkUnit.DAYS, factor.getUnit())) {
+      result = factor.getValue();
+
+    } else if (StringUtils.equals(WorkUnit.HOURS, factor.getUnit())) {
+      result = factor.getValue() / hoursInDay;
+
+    } else if (StringUtils.equals(WorkUnit.MINUTES, factor.getUnit())) {
+      result = factor.getValue() / (hoursInDay * 60.0);
+
+    } else {
+      throw new IllegalArgumentException("Unknown remediation factor unit: " + factor.getUnit());
+    }
+    return result;
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/AbstractFunction.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/AbstractFunction.java
new file mode 100644 (file)
index 0000000..d04d3f0
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions;
+
+import org.sonar.api.rules.Violation;
+import org.sonar.plugins.core.technicaldebt.Requirement;
+import org.sonar.plugins.core.technicaldebt.WorkUnitConverter;
+
+import java.util.Collection;
+
+public abstract class AbstractFunction implements Function {
+
+  private WorkUnitConverter converter;
+
+  public AbstractFunction(WorkUnitConverter converter) {
+    this.converter = converter;
+  }
+
+  protected WorkUnitConverter getConverter() {
+    return converter;
+  }
+
+  public abstract String getKey();
+
+  public abstract double calculateCost(Requirement requirement, Collection<Violation> violations);
+
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/ConstantFunction.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/ConstantFunction.java
new file mode 100644 (file)
index 0000000..91d5763
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions;
+
+import org.sonar.api.rules.Violation;
+import org.sonar.plugins.core.technicaldebt.Requirement;
+import org.sonar.plugins.core.technicaldebt.WorkUnitConverter;
+
+import java.util.Collection;
+
+public final class ConstantFunction extends AbstractFunction {
+
+  public static final String FUNCTION_CONSTANT_RESOURCE = "constant_resource";
+
+  public ConstantFunction(WorkUnitConverter converter) {
+    super(converter);
+  }
+
+  public String getKey() {
+    return FUNCTION_CONSTANT_RESOURCE;
+  }
+
+  public double calculateCost(Requirement requirement, Collection<Violation> violations) {
+    double cost = 0.0;
+    if (!violations.isEmpty()) {
+      cost = getConverter().toDays(requirement.getRemediationFactor());
+    }
+    return cost;
+  }
+
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/Function.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/Function.java
new file mode 100644 (file)
index 0000000..8d6d6ba
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions;
+
+import org.sonar.api.BatchExtension;
+import org.sonar.api.rules.Violation;
+import org.sonar.plugins.core.technicaldebt.Requirement;
+
+import java.util.Collection;
+
+public interface Function extends BatchExtension {
+
+  String getKey();
+
+  double calculateCost(Requirement requirement, Collection<Violation> violations);
+
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/Functions.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/Functions.java
new file mode 100644 (file)
index 0000000..7d149a5
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions;
+
+import com.google.common.collect.Maps;
+import org.sonar.api.BatchExtension;
+import org.sonar.api.rules.Violation;
+import org.sonar.plugins.core.technicaldebt.Requirement;
+
+import java.util.Collection;
+import java.util.Map;
+
+public class Functions implements BatchExtension {
+
+  private final Map<String, Function> functionsByKey = Maps.newHashMap();
+
+  public Functions(final Function[] functions) {
+    for (Function function : functions) {
+      functionsByKey.put(function.getKey(), function);
+    }
+  }
+
+  Function getFunction(String key) {
+    return functionsByKey.get(key);
+  }
+
+  public Function getFunction(Requirement requirement) {
+    return getFunction(requirement.getRemediationFunction());
+  }
+
+  public double calculateCost(Requirement requirement, Collection<Violation> violations) {
+    return getFunction(requirement).calculateCost(requirement, violations);
+  }
+
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearFunction.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearFunction.java
new file mode 100644 (file)
index 0000000..3718e2a
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions;
+
+import org.sonar.api.rules.Violation;
+import org.sonar.plugins.core.technicaldebt.Requirement;
+import org.sonar.plugins.core.technicaldebt.WorkUnitConverter;
+
+import java.util.Collection;
+
+public class LinearFunction extends AbstractFunction {
+
+  public static final String FUNCTION_LINEAR = "linear";
+
+  public static final double DEFAULT_VIOLATION_COST = 1.0;
+
+  public LinearFunction(WorkUnitConverter converter) {
+    super(converter);
+  }
+
+  public String getKey() {
+    return FUNCTION_LINEAR;
+  }
+
+  public double calculateCost(Requirement requirement, Collection<Violation> violations) {
+    double points = 0.0;
+    for (Violation violation : violations) {
+      points += (violation.getCost() != null ? violation.getCost() : DEFAULT_VIOLATION_COST);
+    }
+    return points * getConverter().toDays(requirement.getRemediationFactor());
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithOffsetFunction.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithOffsetFunction.java
new file mode 100644 (file)
index 0000000..5c40240
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions;
+
+import org.sonar.api.rules.Violation;
+import org.sonar.plugins.core.technicaldebt.Requirement;
+import org.sonar.plugins.core.technicaldebt.WorkUnitConverter;
+
+import java.util.Collection;
+
+public final class LinearWithOffsetFunction extends LinearFunction {
+
+  public static final String FUNCTION_LINEAR_WITH_OFFSET = "linear_offset";
+
+  public LinearWithOffsetFunction(WorkUnitConverter converter) {
+    super(converter);
+  }
+
+  public String getKey() {
+    return FUNCTION_LINEAR_WITH_OFFSET;
+  }
+
+  public double calculateCost(Requirement requirement, Collection<Violation> violations) {
+    if (violations.isEmpty()) {
+      return 0.0;
+    }
+    double minimunCost = getConverter().toDays(requirement.getOffset());
+    return minimunCost + super.calculateCost(requirement, violations);
+  }
+
+}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithThresholdFunction.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithThresholdFunction.java
new file mode 100644 (file)
index 0000000..502aa33
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions;
+
+import org.sonar.api.rules.Violation;
+import org.sonar.plugins.core.technicaldebt.Requirement;
+import org.sonar.plugins.core.technicaldebt.WorkUnitConverter;
+
+import java.util.Collection;
+
+public final class LinearWithThresholdFunction extends LinearFunction {
+
+  public static final String FUNCTION_LINEAR_WITH_THRESHOLD = "linear_threshold";
+
+  public LinearWithThresholdFunction(WorkUnitConverter converter) {
+    super(converter);
+  }
+
+  public String getKey() {
+    return FUNCTION_LINEAR_WITH_THRESHOLD;
+  }
+
+  public double calculateCost(Requirement requirement, Collection<Violation> violations) {
+    if (violations.isEmpty()) {
+      return 0.0;
+    }
+    double thresholdCost = getConverter().toDays(requirement.getOffset());
+    double violationsCost = super.calculateCost(requirement, violations);
+    return violationsCost > thresholdCost ? violationsCost : thresholdCost;
+  }
+
+}
index 57dceb2706e86a760bf393f07fb0d7acf321def3..3f4bd1b2a96ef031f32ce6ce889a152d76ac2d04 100644 (file)
@@ -841,6 +841,7 @@ property.category.exclusions.coverage=Code Coverage
 property.category.exclusions.coverage.description=Configure files which should be considered for code coverage.
 property.sonar.coverage.exclusions.name=Coverage Exclusions
 property.sonar.coverage.exclusions.description=Patterns used to exclude some files from coverage report.
+property.category.technicalDebt=Technical Debt
 property.error.notBoolean=Valid options are "true" and "false"
 property.error.notInteger=Only digits are allowed
 property.error.notFloat=Not a floating point number
@@ -2252,6 +2253,18 @@ metric.reopened_issues.description=Reopened issues
 metric.confirmed_issues.name=Confirmed issues
 metric.confirmed_issues.description=Confirmed issues
 
+
+#--------------------------------------------------------------------------------------------------------------------
+#
+# TECHNICAL DEBT METRICS
+#
+#--------------------------------------------------------------------------------------------------------------------
+
+metric.technical_debt.name=Technical Debt
+metric.technical_debt.description=The technical debt is the total effort required to fully adhere all quality requirements defined by a user.
+
+
+
 #------------------------------------------------------------------------------
 #
 # GLOBAL PERMISSIONS
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/RequirementTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/RequirementTest.java
new file mode 100644 (file)
index 0000000..97fb8ce
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt;
+
+import org.junit.Test;
+import org.sonar.api.qualitymodel.Characteristic;
+import org.sonar.plugins.core.technicaldebt.functions.ConstantFunction;
+import org.sonar.plugins.core.technicaldebt.functions.LinearFunction;
+import org.sonar.plugins.core.technicaldebt.functions.LinearWithOffsetFunction;
+import org.sonar.plugins.core.technicaldebt.functions.LinearWithThresholdFunction;
+
+import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public class RequirementTest {
+
+  @Test
+  public void defaultFactor() {
+    Characteristic persistedRequirement = Characteristic.createByName("Efficiency");
+    Requirement requirement = new Requirement(persistedRequirement, null);
+    assertThat(requirement.getRemediationFactor().getValue(), is(WorkUnit.DEFAULT_VALUE));
+    assertThat(requirement.getRemediationFactor().getUnit(), is(WorkUnit.DEFAULT_UNIT));
+  }
+
+  @Test
+  public void testOverriddenFactor() {
+    Characteristic persistedRequirement = Characteristic.createByName("Efficiency");
+    persistedRequirement.setProperty(Requirement.PROPERTY_REMEDIATION_FACTOR, 3.14);
+    Requirement requirement = new Requirement(persistedRequirement, null);
+    assertThat(requirement.getRemediationFactor().getValue(), is(3.14));
+    assertThat(requirement.getRemediationFactor().getUnit(), is(WorkUnit.DAYS));
+  }
+
+  @Test
+  public void defaultFunctionIsLinear() {
+    Characteristic persistedRequirement = Characteristic.createByName("Efficiency");
+    Requirement requirement = new Requirement(persistedRequirement, null);
+    assertThat(requirement.getRemediationFunction(), is(LinearFunction.FUNCTION_LINEAR));
+    assertThat(requirement.getOffset(), is(nullValue()));
+  }
+
+  @Test
+  public void testOverriddenFunction() {
+    Characteristic persistedRequirement = Characteristic.createByName("Efficiency");
+    persistedRequirement.setProperty(Requirement.PROPERTY_REMEDIATION_FUNCTION, ConstantFunction.FUNCTION_CONSTANT_RESOURCE);
+    Requirement requirement = new Requirement(persistedRequirement, null);
+    assertThat(requirement.getRemediationFunction(), is(ConstantFunction.FUNCTION_CONSTANT_RESOURCE));
+  }
+
+  @Test
+  public void testDefaultLinearWithOffset() {
+    Characteristic persistedRequirement = Characteristic.createByName("Efficiency");
+    persistedRequirement.setProperty(Requirement.PROPERTY_REMEDIATION_FUNCTION, LinearWithOffsetFunction.FUNCTION_LINEAR_WITH_OFFSET);
+    Requirement requirement = new Requirement(persistedRequirement, null);
+    assertThat(requirement.getRemediationFunction(), is(LinearWithOffsetFunction.FUNCTION_LINEAR_WITH_OFFSET));
+    assertThat(requirement.getRemediationFactor().getValue(), is(WorkUnit.DEFAULT_VALUE));
+    assertThat(requirement.getRemediationFactor().getUnit(), is(WorkUnit.DEFAULT_UNIT));
+    assertThat(requirement.getOffset().getValue(), is(WorkUnit.DEFAULT_VALUE));
+    assertThat(requirement.getOffset().getUnit(), is(WorkUnit.DEFAULT_UNIT));
+  }
+
+  @Test
+  public void testCustomizedLinearWithOffset() {
+    Characteristic persistedRequirement = Characteristic.createByName("Efficiency");
+    persistedRequirement.setProperty(Requirement.PROPERTY_REMEDIATION_FUNCTION, LinearWithOffsetFunction.FUNCTION_LINEAR_WITH_OFFSET);
+    persistedRequirement.setProperty(Requirement.PROPERTY_OFFSET, 5.0);
+    persistedRequirement.addProperty(persistedRequirement.getProperty(Requirement.PROPERTY_OFFSET).setTextValue("h"));
+    Requirement requirement = new Requirement(persistedRequirement, null);
+    assertThat(requirement.getRemediationFunction(), is(LinearWithOffsetFunction.FUNCTION_LINEAR_WITH_OFFSET));
+    assertThat(requirement.getOffset().getValue(), is(5.0));
+    assertThat(requirement.getOffset().getUnit(), is(WorkUnit.HOURS));
+  }
+
+  @Test
+  public void testDefaultLinearWithThreshold() {
+    Characteristic persistedRequirement = Characteristic.createByName("Efficiency");
+    persistedRequirement.setProperty(Requirement.PROPERTY_REMEDIATION_FUNCTION, LinearWithThresholdFunction.FUNCTION_LINEAR_WITH_THRESHOLD);
+    Requirement requirement = new Requirement(persistedRequirement, null);
+    assertThat(requirement.getRemediationFunction(), is(LinearWithThresholdFunction.FUNCTION_LINEAR_WITH_THRESHOLD));
+    assertThat(requirement.getRemediationFactor().getValue(), is(WorkUnit.DEFAULT_VALUE));
+    assertThat(requirement.getRemediationFactor().getUnit(), is(WorkUnit.DEFAULT_UNIT));
+    assertThat(requirement.getOffset().getValue(), is(WorkUnit.DEFAULT_VALUE));
+    assertThat(requirement.getOffset().getUnit(), is(WorkUnit.DEFAULT_UNIT));
+  }
+
+  @Test
+  public void testCustomizedLinearWithThreshold() {
+    Characteristic persistedRequirement = Characteristic.createByName("Efficiency");
+    persistedRequirement.setProperty(Requirement.PROPERTY_REMEDIATION_FUNCTION, LinearWithThresholdFunction.FUNCTION_LINEAR_WITH_THRESHOLD);
+    persistedRequirement.setProperty(Requirement.PROPERTY_OFFSET, 5.0);
+    persistedRequirement.addProperty(persistedRequirement.getProperty(Requirement.PROPERTY_OFFSET).setTextValue("h"));
+    Requirement requirement = new Requirement(persistedRequirement, null);
+    assertThat(requirement.getRemediationFunction(), is(LinearWithThresholdFunction.FUNCTION_LINEAR_WITH_THRESHOLD));
+    assertThat(requirement.getOffset().getValue(), is(5.0));
+    assertThat(requirement.getOffset().getUnit(), is(WorkUnit.HOURS));
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculatorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculatorTest.java
new file mode 100644 (file)
index 0000000..95205e2
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt;
+
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import org.apache.commons.lang.time.DateUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.MeasuresFilter;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.Violation;
+import org.sonar.plugins.core.technicaldebt.functions.Functions;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+public class TechnicalDebtCalculatorTest {
+
+  private static final Date NOW = new Date(System.currentTimeMillis());
+  private static final Date YESTERDAY = DateUtils.addDays(NOW, -1);
+  private static final Date LAST_MONTH = DateUtils.addMonths(NOW, -1);
+
+  private TechnicalDebtModel technicalDebtModel;
+  private Functions functions;
+  private TechnicalDebtCalculator remediationCostCalculator;
+
+  @Before
+  public void initMocks() {
+    technicalDebtModel = mock(TechnicalDebtModel.class);
+    functions = mock(Functions.class);
+    remediationCostCalculator = new TechnicalDebtCalculator(technicalDebtModel, functions);
+  }
+
+  @Test
+  public void group_violations_by_requirement() throws Exception {
+
+    Requirement requirement1 = mock(Requirement.class);
+    Requirement requirement2 = mock(Requirement.class);
+
+    Violation violation1 = buildViolation("rule1", "repo1", NOW);
+    Violation violation2 = buildViolation("rule1", "repo1", NOW);
+    Violation violation3 = buildViolation("rule2", "repo2", NOW);
+    Violation violation4 = buildViolation("unmatchable", "repo2", NOW);
+
+    List<Violation> violations = Lists.newArrayList(violation1, violation2, violation3, violation4);
+
+    stub(technicalDebtModel.getRequirementByRule("repo1", "rule1")).toReturn(requirement1);
+    stub(technicalDebtModel.getRequirementByRule("repo2", "rule2")).toReturn(requirement2);
+
+    DecoratorContext context = mock(DecoratorContext.class);
+    when(context.getViolations()).thenReturn(violations);
+
+    ListMultimap<Requirement, Violation> groupedViolations = remediationCostCalculator.groupViolations(context);
+
+    assertThat(groupedViolations.keySet().size()).isEqualTo(2);
+    assertThat(groupedViolations.get(requirement1)).containsExactly(violation1, violation2);
+    assertThat(groupedViolations.get(requirement2)).containsExactly(violation3);
+  }
+
+  @Test
+  public void add_cost_with_no_parent() throws Exception {
+
+    double requirementCost = 1.0;
+
+    Requirement requirement = mock(Requirement.class);
+    when(requirement.getParent()).thenReturn(null);
+
+    remediationCostCalculator.updateRequirementCosts(requirement, requirementCost);
+
+    assertThat(remediationCostCalculator.getRequirementCosts().get(requirement)).isEqualTo(requirementCost);
+    assertThat(remediationCostCalculator.getTotal()).isEqualTo(requirementCost);
+  }
+
+  @Test
+  public void add_cost_and_propagate_to_parents() throws Exception {
+
+    double requirementCost = 1.0;
+
+    Characteristic parentCharacteristic = new Characteristic(org.sonar.api.qualitymodel.Characteristic.create());
+
+    Characteristic characteristic = new Characteristic(org.sonar.api.qualitymodel.Characteristic.create(), parentCharacteristic);
+
+    Requirement requirement = mock(Requirement.class);
+    when(requirement.getParent()).thenReturn(characteristic);
+
+    remediationCostCalculator.updateRequirementCosts(requirement, requirementCost);
+
+    assertThat(remediationCostCalculator.getRequirementCosts().get(requirement)).isEqualTo(requirementCost);
+    assertThat(remediationCostCalculator.getCharacteristicCosts().get(characteristic)).isEqualTo(requirementCost);
+    assertThat(remediationCostCalculator.getCharacteristicCosts().get(parentCharacteristic)).isEqualTo(requirementCost);
+  }
+
+  @Test
+  public void compute_totals_costs() throws Exception {
+
+    Requirement requirement1 = mock(Requirement.class);
+    Requirement requirement2 = mock(Requirement.class);
+
+    Violation violation1 = buildViolation("rule1", "repo1", NOW);
+    Violation violation2 = buildViolation("rule1", "repo1", NOW);
+    Violation violation3 = buildViolation("rule2", "repo2", YESTERDAY);
+    Violation violation4 = buildViolation("rule2", "repo2", LAST_MONTH);
+
+    List<Violation> violations = Lists.newArrayList(violation1, violation2, violation3, violation4);
+
+    stub(technicalDebtModel.getRequirementByRule("repo1", "rule1")).toReturn(requirement1);
+    stub(technicalDebtModel.getRequirementByRule("repo2", "rule2")).toReturn(requirement2);
+    stub(technicalDebtModel.getAllRequirements()).toReturn(Lists.newArrayList(requirement1, requirement2));
+
+    stub(functions.calculateCost(any(Requirement.class), any(Collection.class))).toReturn(1.0);
+
+    DecoratorContext context = mock(DecoratorContext.class);
+    stub(context.getViolations()).toReturn(violations);
+    stub(context.getChildrenMeasures(any(MeasuresFilter.class))).toReturn(Collections.EMPTY_LIST);
+
+    remediationCostCalculator.compute(context);
+
+    assertThat(remediationCostCalculator.getTotal()).isEqualTo(2.0);
+  }
+
+  private Violation buildViolation(String ruleKey, String repositoryKey, Date creationDate) {
+    Violation violation = mock(Violation.class);
+    stub(violation.getRule()).toReturn(Rule.create(repositoryKey, ruleKey));
+    stub(violation.getCreatedAt()).toReturn(creationDate);
+    return violation;
+  }
+}
+
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java
new file mode 100644 (file)
index 0000000..14cb4d5
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt;
+
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.qualitymodel.Characteristic;
+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 org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.*;
+
+public class TechnicalDebtDecoratorTest {
+
+  @Test
+  public void generates_metrics() throws Exception {
+    TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(null);
+    assertThat(decorator.generatesMetrics()).hasSize(1);
+  }
+
+  @Test
+  public void execute_on_project() throws Exception {
+    TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(null);
+    assertThat(decorator.shouldExecuteOnProject(null)).isTrue();
+  }
+
+  @Test
+  public void execute_on_source_file() throws Exception {
+    TechnicalDebtCalculator costCalculator = mock(TechnicalDebtCalculator.class);
+    File resource = mock(File.class);
+    DecoratorContext context = mock(DecoratorContext.class);
+
+    TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(costCalculator);
+    decorator.decorate(resource, context);
+
+    verify(costCalculator, times(1)).compute(context);
+  }
+
+  @Test
+  public void not_execute_on_unit_test() throws Exception {
+    TechnicalDebtCalculator costCalculator = mock(TechnicalDebtCalculator.class);
+    File resource = mock(File.class);
+    when(resource.getQualifier()).thenReturn(Qualifiers.UNIT_TEST_FILE);
+    DecoratorContext context = mock(DecoratorContext.class);
+
+    TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(costCalculator);
+    decorator.decorate(resource, context);
+
+    verify(costCalculator, never()).compute(context);
+  }
+
+  @Test
+  public void save_cost_measures() {
+    TechnicalDebtCalculator costCalulator = mock(TechnicalDebtCalculator.class);
+    when(costCalulator.getTotal()).thenReturn(60.0);
+
+    TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(costCalulator);
+    DecoratorContext context = mock(DecoratorContext.class);
+
+    decorator.saveCostMeasures(context);
+
+    verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.TECHNICAL_DEBT, 60.0)));
+  }
+
+  @Test
+  public void always_save_cost_for_positive_values() throws Exception {
+    TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(null);
+
+    // for a project
+    DecoratorContext context = mock(DecoratorContext.class);
+    when(context.getResource()).thenReturn(new Project("foo"));
+    decorator.saveCost(context, null, 12.0, false);
+    verify(context, times(1)).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT));
+
+    // or for a file
+    context = mock(DecoratorContext.class);
+    when(context.getResource()).thenReturn(new File("foo"));
+    decorator.saveCost(context, null, 12.0, false);
+    verify(context, times(1)).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT));
+  }
+
+  @Test
+  public void always_save_cost_for_project_if_top_characteristic() throws Exception {
+    DecoratorContext context = mock(DecoratorContext.class);
+    when(context.getResource()).thenReturn(new Project("foo"));
+    // this is a top characteristic
+    Characteristic topCharacteristic = Characteristic.create();
+
+    TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(null);
+
+    decorator.saveCost(context, topCharacteristic, 0.0, true);
+    verify(context, times(1)).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT).setCharacteristic(topCharacteristic));
+  }
+
+  /**
+   * SQALE-147
+   */
+  @Test
+  public void never_save_cost_for_project_if_not_top_characteristic() throws Exception {
+    DecoratorContext context = mock(DecoratorContext.class);
+    when(context.getResource()).thenReturn(new Project("foo"));
+    Characteristic topCharacteristic = Characteristic.create();
+    Characteristic childCharacteristic = Characteristic.create();
+    topCharacteristic.addChild(childCharacteristic);
+
+    TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(null);
+
+    decorator.saveCost(context, childCharacteristic, 0.0, true);
+    verify(context, never()).saveMeasure(any(Measure.class));
+  }
+
+  @Test
+  public void not_save_cost_for_file_if_zero() throws Exception {
+    DecoratorContext context = mock(DecoratorContext.class);
+    when(context.getResource()).thenReturn(new File("foo"));
+
+    TechnicalDebtDecorator decorator = new TechnicalDebtDecorator(null);
+
+    decorator.saveCost(context, null, 0.0, true);
+    verify(context, never()).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT));
+  }
+
+  @Test
+  public void check_extensions() {
+    assertThat(TechnicalDebtDecorator.extensions()).hasSize(1 /* properties */ + 9 /* extensions */);
+  }
+
+}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtModelTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtModelTest.java
new file mode 100644 (file)
index 0000000..c6222bb
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt;
+
+import org.junit.Test;
+import org.sonar.api.qualitymodel.Characteristic;
+import org.sonar.api.qualitymodel.Model;
+import org.sonar.api.qualitymodel.ModelFinder;
+import org.sonar.api.rules.Rule;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TechnicalDebtModelTest {
+
+  private Characteristic disabledCharacteristic = Characteristic.createByKey("DISABLED", "Disabled").setEnabled(false);
+
+  @Test
+  public void load_model() {
+    ModelFinder modelFinder = mock(ModelFinder.class);
+    Model persistedModel = createModel();
+    when(modelFinder.findByName(TechnicalDebtModel.MODEL_NAME)).thenReturn(persistedModel);
+
+    TechnicalDebtModel technicalDebtModel = new TechnicalDebtModel(modelFinder);
+
+    assertThat(technicalDebtModel.getCharacteristics()).hasSize(2);
+    assertThat(technicalDebtModel.getAllRequirements()).hasSize(1);
+    assertThat(technicalDebtModel.getRequirementByRule("repo", "check1").getRule().getKey()).isEqualTo("check1");
+    assertThat(technicalDebtModel.getRequirementByRule("repo", "check1").getParent().getKey()).isEqualTo("CPU_EFFICIENCY");
+
+    // ignore disabled characteristics/requirements
+    assertThat(technicalDebtModel.getCharacteristics()).excludes(disabledCharacteristic);
+    assertThat(technicalDebtModel.getRequirementByRule("repo", "check2")).isNull();
+  }
+
+  private Model createModel() {
+    Model model = Model.createByName(TechnicalDebtModel.MODEL_NAME);
+    Characteristic efficiency = model.createCharacteristicByKey("EFFICIENCY", "Efficiency");
+    model.createCharacteristicByKey("TESTABILITY", "Testability");
+
+    Characteristic cpuEfficiency = model.createCharacteristicByKey("CPU_EFFICIENCY", "CPU Efficiency");
+    efficiency.addChild(cpuEfficiency);
+
+    Characteristic requirement1 = model.createCharacteristicByRule(Rule.create("repo", "check1", "Check One"));
+    cpuEfficiency.addChild(requirement1);
+
+    // disabled requirement
+    Characteristic requirement2 = model.createCharacteristicByRule(Rule.create("repo", "check2", "Check Two"));
+    requirement2.setEnabled(false);
+    cpuEfficiency.addChild(requirement2);
+
+    // disabled characteristic
+    model.addCharacteristic(disabledCharacteristic);
+    return model;
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/WorkUnitConverterTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/WorkUnitConverterTest.java
new file mode 100644 (file)
index 0000000..39cd912
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class WorkUnitConverterTest {
+
+  @Test
+  public void shouldUseDefaultConfiguration() {
+    WorkUnitConverter converter = new WorkUnitConverter(new PropertiesConfiguration());
+    assertThat(converter.getHoursInDay(), is(WorkUnitConverter.DEFAULT_HOURS_IN_DAY));
+  }
+
+  @Test
+  public void shouldConvertValueToDays() {
+    PropertiesConfiguration configuration = new PropertiesConfiguration();
+    configuration.setProperty(WorkUnitConverter.PROPERTY_HOURS_IN_DAY, "12");
+    WorkUnitConverter converter = new WorkUnitConverter(configuration);
+
+    assertThat(converter.toDays(WorkUnit.create(6.0, WorkUnit.DAYS)), is(6.0));
+    assertThat(converter.toDays(WorkUnit.create(6.0, WorkUnit.HOURS)), is(6.0 / 12.0));
+    assertThat(converter.toDays(WorkUnit.create(60.0 , WorkUnit.MINUTES)), is(1.0/12.0));
+  }
+
+}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/ConstantFunctionTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/ConstantFunctionTest.java
new file mode 100644 (file)
index 0000000..06dca29
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.Violation;
+import org.sonar.plugins.core.technicaldebt.Requirement;
+import org.sonar.plugins.core.technicaldebt.WorkUnit;
+import org.sonar.plugins.core.technicaldebt.WorkUnitConverter;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ConstantFunctionTest {
+
+  private Requirement requirement;
+  private Function function;
+
+  @Before
+  public void before() {
+    function = new ConstantFunction(new WorkUnitConverter(new PropertiesConfiguration()));
+    requirement = mock(Requirement.class);
+    when(requirement.getRemediationFactor()).thenReturn(WorkUnit.createInDays(3.14));
+  }
+
+  @Test
+  public void zeroIfNoViolations() {
+    assertThat(function.calculateCost(requirement, Collections.<Violation>emptyList()), is(0.0));
+  }
+
+  @Test
+  public void countAsIfSingleViolation() {
+    Collection<Violation> violations = Lists.newArrayList();
+
+    Rule rule = Rule.create("checkstyle", "foo", "Foo");
+    violations.add(new Violation(rule));
+    assertThat(function.calculateCost(requirement, violations), is(3.14));
+
+    violations.add(new Violation(rule));
+    assertThat(function.calculateCost(requirement, violations), is(3.14));
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/FunctionsTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/FunctionsTest.java
new file mode 100644 (file)
index 0000000..b3f3346
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions;
+
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class FunctionsTest {
+
+  @Test
+  public void registerFunctions() {
+    Functions functions = new Functions(new Function[]{new LinearFunction(null), new LinearWithOffsetFunction(null),
+      new ConstantFunction(null)});
+    assertThat(functions.getFunction(LinearFunction.FUNCTION_LINEAR)).isInstanceOf(LinearFunction.class);
+    assertThat(functions.getFunction(LinearWithOffsetFunction.FUNCTION_LINEAR_WITH_OFFSET)).isInstanceOf(LinearWithOffsetFunction.class);
+    assertThat(functions.getFunction(ConstantFunction.FUNCTION_CONSTANT_RESOURCE)).isInstanceOf(ConstantFunction.class);
+    assertThat(functions.getFunction("foo")).isNull();
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearFunctionTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearFunctionTest.java
new file mode 100644 (file)
index 0000000..40955e9
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.Violation;
+import org.sonar.plugins.core.technicaldebt.Requirement;
+import org.sonar.plugins.core.technicaldebt.WorkUnit;
+import org.sonar.plugins.core.technicaldebt.WorkUnitConverter;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class LinearFunctionTest {
+
+  private Requirement requirement;
+  private Function function = new LinearFunction(new WorkUnitConverter(new PropertiesConfiguration()));
+
+  @Before
+  public void before() {
+    requirement = mock(Requirement.class);
+    when(requirement.getRemediationFactor()).thenReturn(WorkUnit.createInDays(3.14));
+  }
+
+  @Test
+  public void zeroIfNoViolations() {
+    assertThat(function.calculateCost(requirement, Collections.<Violation>emptyList()), is(0.0));
+  }
+
+  @Test
+  public void countEveryViolation() {
+    Collection<Violation> violations = Lists.newArrayList();
+
+    Rule rule = Rule.create("checkstyle", "foo", "Foo");
+    violations.add(new Violation(rule));
+    assertThat(function.calculateCost(requirement, violations), is(3.14));
+
+    violations.add(new Violation(rule));
+    assertThat(function.calculateCost(requirement, violations), is(3.14 * 2));
+  }
+
+  @Test
+  public void usePointsWhenAvailable() {
+    Collection<Violation> violations = Lists.newArrayList();
+
+    Rule rule = Rule.create("checkstyle", "foo", "Foo");
+    violations.add(new Violation(rule).setCost(20.5));
+    violations.add(new Violation(rule).setCost(3.8));
+    violations.add(new Violation(rule));
+    assertThat(function.calculateCost(requirement, violations), is(3.14 * (20.5 + 3.8 + 1)));
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithOffsetFunctionTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithOffsetFunctionTest.java
new file mode 100644 (file)
index 0000000..46fa3d9
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.Violation;
+import org.sonar.plugins.core.technicaldebt.Requirement;
+import org.sonar.plugins.core.technicaldebt.WorkUnit;
+import org.sonar.plugins.core.technicaldebt.WorkUnitConverter;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class LinearWithOffsetFunctionTest {
+
+  private Requirement requirement;
+  private Function function = new LinearWithOffsetFunction(new WorkUnitConverter(new PropertiesConfiguration()));
+
+  @Before
+  public void before() {
+    requirement = mock(Requirement.class);
+    when(requirement.getRemediationFactor()).thenReturn(WorkUnit.createInDays(3.14));
+    when(requirement.getOffset()).thenReturn(WorkUnit.createInDays(2.12));
+  }
+
+  @Test
+  public void zeroIfNoViolations() {
+    assertThat(function.calculateCost(requirement, Collections.<Violation>emptyList()), is(0.0));
+  }
+
+  @Test
+  public void countEveryViolation() {
+    Collection<Violation> violations = Lists.newArrayList();
+
+    Rule rule = Rule.create("checkstyle", "foo", "Foo");
+    violations.add(new Violation(rule));
+    assertThat(function.calculateCost(requirement, violations), is(2.12 + 3.14));
+
+    violations.add(new Violation(rule));
+    assertThat(function.calculateCost(requirement, violations), is(2.12 + 3.14 * 2));
+  }
+}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithThresholdFunctionTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithThresholdFunctionTest.java
new file mode 100644 (file)
index 0000000..51e17f2
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.plugins.core.technicaldebt.functions;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.Violation;
+import org.sonar.plugins.core.technicaldebt.Requirement;
+import org.sonar.plugins.core.technicaldebt.WorkUnit;
+import org.sonar.plugins.core.technicaldebt.WorkUnitConverter;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class LinearWithThresholdFunctionTest {
+
+  private Requirement requirement;
+  private Function function = new LinearWithThresholdFunction(new WorkUnitConverter(new PropertiesConfiguration()));
+
+  @Before
+  public void before() {
+    requirement = mock(Requirement.class);
+    when(requirement.getRemediationFactor()).thenReturn(WorkUnit.createInDays(2.0));
+    when(requirement.getOffset()).thenReturn(WorkUnit.createInDays(5.0));
+  }
+
+  @Test
+  public void zeroIfNoViolations() {
+    assertThat(function.calculateCost(requirement, Collections.<Violation>emptyList()), is(0.0));
+  }
+
+  @Test
+  public void countEveryViolationAndCheckThreshold() {
+    Collection<Violation> violations = Lists.newArrayList();
+
+    Rule rule = Rule.create("checkstyle", "foo", "Foo");
+    violations.add(new Violation(rule));
+    assertThat(function.calculateCost(requirement, violations), is(5.0));
+
+    violations.add(new Violation(rule));
+    assertThat(function.calculateCost(requirement, violations), is(5.0));
+
+    violations.add(new Violation(rule));
+    assertThat(function.calculateCost(requirement, violations), is(6.0));
+  }
+}
index cc2cfaebf7b82d013464452c8ad8ab8274343c5a..b410c6bc2953963a5e2a3482342cf88fe7f2fb2f 100644 (file)
@@ -121,6 +121,12 @@ public interface CoreProperties {
    */
   String CATEGORY_LICENSES = "licenses";
 
+  /**
+   * @since 4.0
+   */
+  String CATEGORY_TECHNICAL_DEBT = "technicalDebt";
+
+
   /* Global settings */
   String SONAR_HOME = "SONAR_HOME";
   String PROJECT_BRANCH_PROPERTY = "sonar.branch";
index 8cba132e0dd7d6376ce420437cd56cc67101a8cb..5616e6a5690553b7ad42df1a143036fc487a86b2 100644 (file)
@@ -55,6 +55,11 @@ public final class CoreMetrics {
   public static String DOMAIN_DUPLICATION = "Duplication";
   public static String DOMAIN_DESIGN = "Design";
 
+  /**
+   * @since 4.0
+   */
+  public static String DOMAIN_TECHNICAL_DEBT = "Technical Debt";
+
   public static final String LINES_KEY = "lines";
   public static final Metric LINES = new Metric.Builder(LINES_KEY, "Lines", Metric.ValueType.INT)
       .setDescription("Lines")
@@ -2009,6 +2014,30 @@ public final class CoreMetrics {
       .setHidden(true)
       .create();
 
+
+  // --------------------------------------------------------------------------------------------------------------------
+  //
+  // TECHNICAL DEBT
+  //
+  // --------------------------------------------------------------------------------------------------------------------
+
+  /**
+   * @since 4.0
+   */
+  public static final String TECHNICAL_DEBT_KEY = "technical_debt";
+
+  /**
+   * @since 4.0
+   */
+  public static final Metric TECHNICAL_DEBT = new Metric.Builder(TECHNICAL_DEBT_KEY, "Technical Debt", Metric.ValueType.FLOAT)
+    .setDomain(DOMAIN_TECHNICAL_DEBT)
+    .setDirection(Metric.DIRECTION_WORST)
+    .setOptimizedBestValue(true)
+    .setBestValue(0.0)
+    .setQualitative(true)
+    .create();
+
+
   // --------------------------------------------------------------------------------------------------------------------
   //
   // FILE DATA
index 4f6e1a9e6184b8b2f1ab241f26584156718ff69e..14abb67a8bed224b935d61f99ca4a0235654d8cd 100644 (file)
@@ -31,7 +31,7 @@ public class CoreMetricsTest {
   @Test
   public void shouldReadMetricsFromClassReflection() {
     List<Metric> metrics = CoreMetrics.getMetrics();
-    assertThat(metrics).hasSize(148);
+    assertThat(metrics).hasSize(149);
     assertThat(metrics).contains(CoreMetrics.NCLOC, CoreMetrics.DIRECTORIES);
   }
 }
diff --git a/sonar-server/src/main/java/org/sonar/server/technicaldebt/XMLConstants.java b/sonar-server/src/main/java/org/sonar/server/technicaldebt/XMLConstants.java
deleted file mode 100644 (file)
index 6228da4..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.technicaldebt;
-
-public final class XMLConstants {
-
-  private XMLConstants(){
-    // Utility class
-  }
-
-  public static final String CHARACTERISTIC = "chc";
-  public static final String CHARACTERISTIC_KEY = "key";
-  public static final String CHARACTERISTIC_NAME = "name";
-  public static final String CHARACTERISTIC_DESCRIPTION = "desc";
-  public static final String PROPERTY = "prop";
-  public static final String PROPERTY_KEY = "key";
-  public static final String PROPERTY_VALUE = "val";
-  public static final String PROPERTY_TEXT_VALUE = "txt";
-}
index 8ffa024f8de06a571d5e1f3423bb7a557cf98859..4cf63de05d4e11b0641c57d7648dcedcf60e37a5 100644 (file)
@@ -46,22 +46,31 @@ public class XMLImporter implements ServerExtension {
 
   private static final Logger LOG = LoggerFactory.getLogger(XMLImporter.class);
 
+  private static final String CHARACTERISTIC = "chc";
+  private static final String CHARACTERISTIC_KEY = "key";
+  private static final String CHARACTERISTIC_NAME = "name";
+  private static final String CHARACTERISTIC_DESCRIPTION = "desc";
+  private static final String PROPERTY = "prop";
+  private static final String PROPERTY_KEY = "key";
+  private static final String PROPERTY_VALUE = "val";
+  private static final String PROPERTY_TEXT_VALUE = "txt";
+
   public Model importXML(String xml, ValidationMessages messages, RuleCache ruleCache) {
     return importXML(new StringReader(xml), messages, ruleCache);
   }
 
   public Model importXML(Reader xml, ValidationMessages messages, RuleCache repositoryCache) {
-    Model sqale = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
+    Model model = Model.createByName(RegisterTechnicalDebtModel.TECHNICAL_DEBT_MODEL);
     try {
       SMInputFactory inputFactory = initStax();
       SMHierarchicCursor cursor = inputFactory.rootElementCursor(xml);
 
       // advance to <sqale>
       cursor.advance();
-      SMInputCursor chcCursor = cursor.childElementCursor(XMLConstants.CHARACTERISTIC);
+      SMInputCursor chcCursor = cursor.childElementCursor(CHARACTERISTIC);
 
       while (chcCursor.getNext() != null) {
-        processCharacteristic(sqale, chcCursor, messages, repositoryCache);
+        processCharacteristic(model, chcCursor, messages, repositoryCache);
       }
 
       cursor.getStreamReader().closeCompletely();
@@ -70,7 +79,7 @@ public class XMLImporter implements ServerExtension {
       LOG.error("XML is not valid", e);
       messages.addErrorText("XML is not valid: " + e.getMessage());
     }
-    return sqale;
+    return model;
   }
 
   private SMInputFactory initStax() {
@@ -82,7 +91,7 @@ public class XMLImporter implements ServerExtension {
     return new SMInputFactory(xmlFactory);
   }
 
-  private Characteristic processCharacteristic(Model sqale, SMInputCursor chcCursor, ValidationMessages messages, RuleCache ruleCache) throws XMLStreamException {
+  private Characteristic processCharacteristic(Model model, SMInputCursor chcCursor, ValidationMessages messages, RuleCache ruleCache) throws XMLStreamException {
     Characteristic characteristic = Characteristic.create();
     SMInputCursor cursor = chcCursor.childElementCursor();
 
@@ -90,20 +99,20 @@ public class XMLImporter implements ServerExtension {
     List<Characteristic> children = Lists.newArrayList();
     while (cursor.getNext() != null) {
       String node = cursor.getLocalName();
-      if (StringUtils.equals(node, XMLConstants.CHARACTERISTIC_KEY)) {
+      if (StringUtils.equals(node, CHARACTERISTIC_KEY)) {
         characteristic.setKey(cursor.collectDescendantText().trim());
 
-      } else if (StringUtils.equals(node, XMLConstants.CHARACTERISTIC_NAME)) {
+      } else if (StringUtils.equals(node, CHARACTERISTIC_NAME)) {
         characteristic.setName(cursor.collectDescendantText().trim(), false);
 
-      } else if (StringUtils.equals(node, XMLConstants.CHARACTERISTIC_DESCRIPTION)) {
+      } else if (StringUtils.equals(node, CHARACTERISTIC_DESCRIPTION)) {
         characteristic.setDescription(cursor.collectDescendantText().trim());
 
-      } else if (StringUtils.equals(node, XMLConstants.PROPERTY)) {
+      } else if (StringUtils.equals(node, PROPERTY)) {
         processProperty(characteristic, cursor, messages);
 
-      } else if (StringUtils.equals(node, XMLConstants.CHARACTERISTIC)) {
-        children.add(processCharacteristic(sqale, cursor, messages, ruleCache));
+      } else if (StringUtils.equals(node, CHARACTERISTIC)) {
+        children.add(processCharacteristic(model, cursor, messages, ruleCache));
 
       } else if (StringUtils.equals(node, "rule-repo")) {
         ruleRepositoryKey = cursor.collectDescendantText().trim();
@@ -115,7 +124,7 @@ public class XMLImporter implements ServerExtension {
     fillRule(characteristic, ruleRepositoryKey, ruleKey, messages, ruleCache);
 
     if (StringUtils.isNotBlank(characteristic.getKey()) || characteristic.getRule() != null) {
-      addCharacteristicToModel(sqale, characteristic, children);
+      addCharacteristicToModel(model, characteristic, children);
       return characteristic;
     }
     return null;
@@ -133,11 +142,11 @@ public class XMLImporter implements ServerExtension {
     }
   }
 
-  private void addCharacteristicToModel(Model sqale, Characteristic characteristic, List<Characteristic> children) {
-    sqale.addCharacteristic(characteristic);
+  private void addCharacteristicToModel(Model model, Characteristic characteristic, List<Characteristic> children) {
+    model.addCharacteristic(characteristic);
     for (Characteristic child : children) {
       if (child != null) {
-        sqale.addCharacteristic(child);
+        model.addCharacteristic(child);
         characteristic.addChild(child);
       }
     }
@@ -150,10 +159,10 @@ public class XMLImporter implements ServerExtension {
     String textValue = null;
     while (c.getNext() != null) {
       String node = c.getLocalName();
-      if (StringUtils.equals(node, XMLConstants.PROPERTY_KEY)) {
+      if (StringUtils.equals(node, PROPERTY_KEY)) {
         key = c.collectDescendantText().trim();
 
-      } else if (StringUtils.equals(node, XMLConstants.PROPERTY_VALUE)) {
+      } else if (StringUtils.equals(node, PROPERTY_VALUE)) {
         String s = c.collectDescendantText().trim();
         try {
           value = NumberUtils.createDouble(s);
@@ -162,7 +171,7 @@ public class XMLImporter implements ServerExtension {
           LOG.error(message, ex);
           messages.addErrorText(message);
         }
-      } else if (StringUtils.equals(node, XMLConstants.PROPERTY_TEXT_VALUE)) {
+      } else if (StringUtils.equals(node, PROPERTY_TEXT_VALUE)) {
         textValue = c.collectDescendantText().trim();
       }
     }