aboutsummaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@gmail.com>2013-09-27 14:06:47 +0200
committerJulien Lancelot <julien.lancelot@gmail.com>2013-09-27 14:06:58 +0200
commit42cab973f76e48d80b2d5fb621aaa983ad940254 (patch)
treef5ecab7303736d8a28adaa8ad1e61fc6eda81c6f /plugins
parent7bf08fcfdac86a4ada7ad739be733f437d0b95fb (diff)
downloadsonarqube-42cab973f76e48d80b2d5fb621aaa983ad940254.tar.gz
sonarqube-42cab973f76e48d80b2d5fb621aaa983ad940254.zip
SONAR-4715 Compute the new "Technical Debt" metric
Diffstat (limited to 'plugins')
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java2
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Characteristic.java74
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Characteristicable.java28
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Requirement.java89
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculator.java138
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java132
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtModel.java97
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/WorkUnit.java68
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/WorkUnitConverter.java58
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/AbstractFunction.java44
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/ConstantFunction.java48
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/Function.java34
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/Functions.java52
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearFunction.java49
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithOffsetFunction.java48
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithThresholdFunction.java49
-rw-r--r--plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties13
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/RequirementTest.java115
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculatorTest.java153
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java152
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtModelTest.java74
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/WorkUnitConverterTest.java47
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/ConstantFunctionTest.java68
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/FunctionsTest.java37
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearFunctionTest.java78
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithOffsetFunctionTest.java68
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithThresholdFunctionTest.java71
27 files changed, 1886 insertions, 0 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
index e672a9d846c..d0315062434 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
@@ -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
index 00000000000..a3b7cbfe3a4
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Characteristic.java
@@ -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
index 00000000000..5beeb7263f1
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Characteristicable.java
@@ -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
index 00000000000..c10dc52f2a0
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/Requirement.java
@@ -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
index 00000000000..0c4e737ec96
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculator.java
@@ -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
index 00000000000..00d11f46343
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java
@@ -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
index 00000000000..b5a969d363b
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtModel.java
@@ -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
index 00000000000..f5d20dbb3e9
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/WorkUnit.java
@@ -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
index 00000000000..0255f160e71
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/WorkUnitConverter.java
@@ -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
index 00000000000..d04d3f0dd99
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/AbstractFunction.java
@@ -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
index 00000000000..91d57632acd
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/ConstantFunction.java
@@ -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
index 00000000000..8d6d6ba564d
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/Function.java
@@ -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
index 00000000000..7d149a5ae53
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/Functions.java
@@ -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
index 00000000000..3718e2a6967
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearFunction.java
@@ -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
index 00000000000..5c402408db5
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithOffsetFunction.java
@@ -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
index 00000000000..502aa33202e
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithThresholdFunction.java
@@ -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;
+ }
+
+}
diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
index 57dceb2706e..3f4bd1b2a96 100644
--- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
+++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
@@ -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
index 00000000000..97fb8ce4026
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/RequirementTest.java
@@ -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
index 00000000000..95205e2084f
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtCalculatorTest.java
@@ -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
index 00000000000..14cb4d56353
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java
@@ -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
index 00000000000..c6222bbdd6d
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtModelTest.java
@@ -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
index 00000000000..39cd912a36a
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/WorkUnitConverterTest.java
@@ -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
index 00000000000..06dca29af8c
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/ConstantFunctionTest.java
@@ -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
index 00000000000..b3f33466bdd
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/FunctionsTest.java
@@ -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
index 00000000000..40955e95089
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearFunctionTest.java
@@ -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
index 00000000000..46fa3d92b2c
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithOffsetFunctionTest.java
@@ -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
index 00000000000..51e17f25966
--- /dev/null
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/functions/LinearWithThresholdFunctionTest.java
@@ -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));
+ }
+}