]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5056 Read debt from rule during analysis
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 11 Mar 2014 07:09:01 +0000 (08:09 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 11 Mar 2014 07:09:01 +0000 (08:09 +0100)
44 files changed:
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java
sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java
sonar-batch/src/main/java/org/sonar/batch/debt/DebtModelLoader.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/debt/DebtModelProvider.java
sonar-batch/src/main/java/org/sonar/batch/debt/RuleDebtCalculator.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java
sonar-batch/src/main/java/org/sonar/batch/rule/RulesProvider.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
sonar-batch/src/test/java/org/sonar/batch/debt/DebtModelLoaderTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/debt/DebtModelProviderTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/debt/RuleDebtCalculatorTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/debt/TechnicalDebtModelProviderTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java
sonar-batch/src/test/java/org/sonar/batch/rule/RulesProviderTest.java [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_default_and_user_debt_definitions.xml [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_default_debt_definitions.xml [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_user_debt_definitions.xml [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/fail_if_characteristic_not_found.xml [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/fail_if_no_function.xml [new file with mode: 0644]
sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/shared.xml [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/rule/RuleDao.java
sonar-core/src/main/java/org/sonar/core/rule/RuleMapper.java
sonar-core/src/main/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtManager.java
sonar-core/src/main/resources/org/sonar/core/rule/RuleMapper.xml
sonar-core/src/test/java/org/sonar/core/rule/RuleDaoTest.java
sonar-core/src/test/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtModelTest.java
sonar-core/src/test/resources/org/sonar/core/rule/RuleDaoTest/select_enables_and_non_manual.xml [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rule.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/RuleParam.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rules.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRule.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRules.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/NewRule.java
sonar-plugin-api/src/main/java/org/sonar/api/rule/RemediationFunction.java
sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/Characteristic.java
sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/Requirement.java
sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/TechnicalDebtModel.java
sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/internal/DefaultRequirement.java
sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/server/Characteristic.java
sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/server/TechnicalDebtManager.java
sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/server/internal/DefaultCharacteristic.java
sonar-plugin-api/src/main/java/org/sonar/api/utils/Duration.java
sonar-plugin-api/src/test/java/org/sonar/api/utils/DurationTest.java

index 8d4c893f436b6d37ff3185c0fac08cd572000850..359f348cc7c0fc68e8841c5a001009277b44170c 100644 (file)
@@ -24,10 +24,12 @@ import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ListMultimap;
-import org.slf4j.LoggerFactory;
 import org.sonar.api.CoreProperties;
 import org.sonar.api.PropertyType;
 import org.sonar.api.batch.*;
+import org.sonar.api.batch.rule.Rule;
+import org.sonar.api.batch.rule.Rules;
+import org.sonar.api.batch.rule.internal.DefaultRule;
 import org.sonar.api.component.ResourcePerspectives;
 import org.sonar.api.config.PropertyDefinition;
 import org.sonar.api.issue.Issuable;
@@ -37,10 +39,13 @@ import org.sonar.api.measures.*;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.resources.ResourceUtils;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RulePriority;
 import org.sonar.api.technicaldebt.batch.Characteristic;
-import org.sonar.api.technicaldebt.batch.Requirement;
 import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
 
+import javax.annotation.Nullable;
+
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
@@ -57,10 +62,12 @@ public final class TechnicalDebtDecorator implements Decorator {
 
   private final ResourcePerspectives perspectives;
   private final TechnicalDebtModel model;
+  private final Rules rules;
 
-  public TechnicalDebtDecorator(ResourcePerspectives perspectives, TechnicalDebtModel model) {
+  public TechnicalDebtDecorator(ResourcePerspectives perspectives, TechnicalDebtModel model, Rules rules) {
     this.perspectives = perspectives;
     this.model = model;
+    this.rules = rules;
   }
 
   public boolean shouldExecuteOnProject(Project project) {
@@ -81,25 +88,29 @@ public final class TechnicalDebtDecorator implements Decorator {
   }
 
   private void saveMeasures(DecoratorContext context, List<Issue> issues) {
-    // group issues by requirement
-    ListMultimap<Requirement, Issue> issuesByRequirement = issuesByRequirement(issues);
+    // group issues by rules
+    ListMultimap<Rule, Issue> issuesByRule = issuesByRule(issues);
 
     double total = 0.0;
     Map<Characteristic, Double> characteristicCosts = newHashMap();
-    Map<Requirement, Double> requirementCosts = newHashMap();
-
-    for (Requirement requirement : model.requirements()) {
-      List<Issue> requirementIssues = issuesByRequirement.get(requirement);
-      double value = computeTechnicalDebt(CoreMetrics.TECHNICAL_DEBT, context, requirement, requirementIssues);
-
-      requirementCosts.put(requirement, value);
-      total += value;
-      propagateTechnicalDebtInParents(requirement.characteristic(), value, characteristicCosts);
+    Map<Rule, Double> ruleDebtCosts = newHashMap();
+
+    for (Rule rule : rules.findAll()) {
+      String characteristicKey = rule.characteristic();
+      if (characteristicKey != null) {
+        List<Issue> requirementIssues = issuesByRule.get(rule);
+        double value = computeTechnicalDebt(CoreMetrics.TECHNICAL_DEBT, context, rule, requirementIssues);
+
+        ruleDebtCosts.put(rule, value);
+        total += value;
+        Characteristic characteristic = model.characteristicByKey(characteristicKey);
+        propagateTechnicalDebtInParents(characteristic, value, characteristicCosts);
+      }
     }
 
     context.saveMeasure(CoreMetrics.TECHNICAL_DEBT, total);
     saveOnCharacteristic(context, characteristicCosts);
-    saveOnRequirement(context, requirementCosts);
+    saveOnRequirement(context, ruleDebtCosts);
   }
 
   private void saveOnCharacteristic(DecoratorContext context, Map<Characteristic, Double> characteristicCosts) {
@@ -108,8 +119,8 @@ public final class TechnicalDebtDecorator implements Decorator {
     }
   }
 
-  private void saveOnRequirement(DecoratorContext context, Map<Requirement, Double> requirementCosts) {
-    for (Map.Entry<Requirement, Double> entry : requirementCosts.entrySet()) {
+  private void saveOnRequirement(DecoratorContext context, Map<Rule, Double> requirementCosts) {
+    for (Map.Entry<Rule, Double> entry : requirementCosts.entrySet()) {
       saveTechnicalDebt(context, entry.getKey(), entry.getValue(), ResourceUtils.isEntity(context.getResource()));
     }
   }
@@ -126,17 +137,17 @@ public final class TechnicalDebtDecorator implements Decorator {
   }
 
   @VisibleForTesting
-  void saveTechnicalDebt(DecoratorContext context, Requirement requirement, Double value, boolean inMemory) {
+  void saveTechnicalDebt(DecoratorContext context, Rule rule, 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) {
-      Measure measure = new Measure(CoreMetrics.TECHNICAL_DEBT);
-      measure.setRequirement(requirement);
+      org.sonar.api.rules.Rule oldRule = toOldRule(rule);
+      RuleMeasure measure = new RuleMeasure(CoreMetrics.TECHNICAL_DEBT, oldRule, oldRule.getSeverity(), null);
       saveMeasure(context, measure, value, inMemory);
     }
   }
 
-  private void saveMeasure(DecoratorContext context, Measure measure, Double value, boolean inMemory){
+  private void saveMeasure(DecoratorContext context, Measure measure, Double value, boolean inMemory) {
     measure.setValue(value);
     if (inMemory) {
       measure.setPersistenceMode(PersistenceMode.MEMORY);
@@ -145,22 +156,17 @@ public final class TechnicalDebtDecorator implements Decorator {
   }
 
   @VisibleForTesting
-  ListMultimap<Requirement, Issue> issuesByRequirement(List<Issue> issues) {
-    ListMultimap<Requirement, Issue> issuesByRequirement = ArrayListMultimap.create();
+  ListMultimap<Rule, Issue> issuesByRule(List<Issue> issues) {
+    ListMultimap<Rule, Issue> result = ArrayListMultimap.create();
     for (Issue issue : issues) {
-      String repositoryKey = issue.ruleKey().repository();
-      String key = issue.ruleKey().rule();
-      Requirement requirement = model.requirementsByRule(issue.ruleKey());
-      if (requirement == null) {
-        LoggerFactory.getLogger(getClass()).debug("No technical debt requirement for: " + repositoryKey + "/" + key);
-      } else {
-        issuesByRequirement.put(requirement, issue);
-      }
+      RuleKey key = issue.ruleKey();
+      Rule rule = rules.find(key);
+      result.put(rule, issue);
     }
-    return issuesByRequirement;
+    return result;
   }
 
-  private double computeTechnicalDebt(Metric metric, DecoratorContext context, Requirement requirement, Collection<Issue> issues) {
+  private double computeTechnicalDebt(Metric metric, DecoratorContext context, Rule rule, Collection<Issue> issues) {
     long debt = 0L;
     if (issues != null) {
       for (Issue issue : issues) {
@@ -171,16 +177,18 @@ public final class TechnicalDebtDecorator implements Decorator {
       }
     }
 
-    for (Measure measure : context.getChildrenMeasures(MeasuresFilters.requirement(metric, requirement))) {
-      Requirement measureRequirement = measure.getRequirement();
-      if (measureRequirement != null && measureRequirement.equals(requirement) && measure.getValue() != null) {
+    org.sonar.api.rules.Rule oldRule = toOldRule(rule);
+    for (Measure measure : context.getChildrenMeasures(MeasuresFilters.rule(metric, oldRule))) {
+      // Comparison on rule is only used for unit test, otherwise no need to do this check
+      RuleMeasure ruleMeasure = (RuleMeasure) measure;
+      if (measure != null && ruleMeasure.getRule().equals(oldRule) && measure.getValue() != null) {
         debt += measure.getValue();
       }
     }
     return debt;
   }
 
-  private void propagateTechnicalDebtInParents(Characteristic characteristic, double value, Map<Characteristic, Double> characteristicCosts) {
+  private void propagateTechnicalDebtInParents(@Nullable Characteristic characteristic, double value, Map<Characteristic, Double> characteristicCosts) {
     if (characteristic != null) {
       Double parentCost = characteristicCosts.get(characteristic);
       if (parentCost == null) {
@@ -207,4 +215,13 @@ public final class TechnicalDebtDecorator implements Decorator {
         .build()
     );
   }
+
+  private org.sonar.api.rules.Rule toOldRule(Rule rule) {
+    DefaultRule defaultRule = (DefaultRule) rule;
+    org.sonar.api.rules.Rule oldRule = org.sonar.api.rules.Rule.create(rule.key().repository(), rule.key().rule());
+    oldRule.setSeverity(RulePriority.valueOf(rule.severity()));
+    oldRule.setId(defaultRule.id());
+    return oldRule;
+  }
+
 }
index 248e29211928279f3d4aaca145857b7c53c6956d..cc98836f0493827ada3db67d2071afb9e828b220 100644 (file)
@@ -32,23 +32,21 @@ import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.rule.Rule;
+import org.sonar.api.batch.rule.Rules;
+import org.sonar.api.batch.rule.internal.RulesBuilder;
 import org.sonar.api.component.ResourcePerspectives;
 import org.sonar.api.issue.Issuable;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.Measure;
-import org.sonar.api.measures.MeasuresFilter;
-import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.*;
 import org.sonar.api.resources.File;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.technicaldebt.batch.Characteristic;
-import org.sonar.api.technicaldebt.batch.Requirement;
 import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
 import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
-import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement;
 import org.sonar.api.test.IsMeasure;
 import org.sonar.api.utils.Duration;
 
@@ -78,14 +76,24 @@ public class TechnicalDebtDecoratorTest {
   @Mock
   Issuable issuable;
 
+  @Mock
+  ResourcePerspectives perspectives;
+
+  RuleKey ruleKey1 = RuleKey.of("repo1", "rule1");
+  RuleKey ruleKey2 = RuleKey.of("repo2", "rule2");
+  Rules rules;
+
   TechnicalDebtDecorator decorator;
 
   @Before
   public void before() throws Exception {
-    ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
     when(perspectives.as(Issuable.class, resource)).thenReturn(issuable);
+    RulesBuilder rulesBuilder = new RulesBuilder();
+    rulesBuilder.add(ruleKey1).setName("rule1").setCharacteristic("MEMORY_EFFICIENCY");
+    rulesBuilder.add(ruleKey2).setName("rule2").setCharacteristic("MODULARITY");
+    rules = rulesBuilder.build();
 
-    decorator = new TechnicalDebtDecorator(perspectives, defaultTechnicalDebtModel);
+    decorator = new TechnicalDebtDecorator(perspectives, defaultTechnicalDebtModel, rules);
   }
 
   @Test
@@ -109,24 +117,17 @@ public class TechnicalDebtDecoratorTest {
 
   @Test
   public void group_issues_by_requirement() throws Exception {
-    Requirement requirement1 = mock(Requirement.class);
-    Requirement requirement2 = mock(Requirement.class);
-
     Issue issue1 = createIssue("rule1", "repo1");
     Issue issue2 = createIssue("rule1", "repo1");
     Issue issue3 = createIssue("rule2", "repo2");
-    Issue issue4 = createIssue("unmatchable", "repo2");
-
-    List<Issue> issues = newArrayList(issue1, issue2, issue3, issue4);
 
-    when(defaultTechnicalDebtModel.requirementsByRule(RuleKey.of("repo1", "rule1"))).thenReturn(requirement1);
-    when(defaultTechnicalDebtModel.requirementsByRule(RuleKey.of("repo2", "rule2"))).thenReturn(requirement2);
+    List<Issue> issues = newArrayList(issue1, issue2, issue3);
 
-    ListMultimap<Requirement, Issue> result = decorator.issuesByRequirement(issues);
+    ListMultimap<Rule, Issue> result = decorator.issuesByRule(issues);
 
     assertThat(result.keySet().size()).isEqualTo(2);
-    assertThat(result.get(requirement1)).containsExactly(issue1, issue2);
-    assertThat(result.get(requirement2)).containsExactly(issue3);
+    assertThat(result.get(rules.find(ruleKey1))).containsExactly(issue1, issue2);
+    assertThat(result.get(rules.find(ruleKey2))).containsExactly(issue3);
   }
 
   @Test
@@ -134,14 +135,10 @@ public class TechnicalDebtDecoratorTest {
     Issue issue = createIssue("rule1", "repo1").setDebt(Duration.create(ONE_DAY_IN_MINUTES));
     when(issuable.issues()).thenReturn(newArrayList(issue));
 
-    Requirement requirement = mock(Requirement.class);
-    when(defaultTechnicalDebtModel.requirementsByRule(RuleKey.of("repo1", "rule1"))).thenReturn(requirement);
-    doReturn(newArrayList(requirement)).when(defaultTechnicalDebtModel).requirements();
-
     decorator.decorate(resource, context);
 
     verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, ONE_DAY_IN_MINUTES.doubleValue());
-    verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, null, requirement, ONE_DAY_IN_MINUTES.doubleValue())));
+    verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, ONE_DAY_IN_MINUTES.doubleValue())));
   }
 
   @Test
@@ -149,10 +146,6 @@ public class TechnicalDebtDecoratorTest {
     Issue issue = createIssue("rule1", "repo1").setDebt(null);
     when(issuable.issues()).thenReturn(newArrayList(issue));
 
-    Requirement requirement = mock(Requirement.class);
-    when(defaultTechnicalDebtModel.requirementsByRule(RuleKey.of("repo1", "rule1"))).thenReturn(requirement);
-    doReturn(newArrayList(requirement)).when(defaultTechnicalDebtModel).requirements();
-
     decorator.decorate(resource, context);
 
     verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 0.0);
@@ -163,20 +156,16 @@ public class TechnicalDebtDecoratorTest {
     Issue issue = createIssue("rule1", "repo1").setDebt(Duration.create(ONE_DAY_IN_MINUTES));
     when(issuable.issues()).thenReturn(newArrayList(issue));
 
-    DefaultCharacteristic parentCharacteristic = new DefaultCharacteristic().setKey("parentCharacteristic");
-    DefaultCharacteristic characteristic = new DefaultCharacteristic().setKey("characteristic").setParent(parentCharacteristic);
-    RuleKey ruleKey = RuleKey.of("repo1", "rule1");
-    DefaultRequirement requirement = new DefaultRequirement().setCharacteristic(characteristic).setRuleKey(ruleKey);
-
-    when(defaultTechnicalDebtModel.requirementsByRule(ruleKey)).thenReturn(requirement);
-    doReturn(newArrayList(requirement)).when(defaultTechnicalDebtModel).requirements();
+    DefaultCharacteristic parentCharacteristic = new DefaultCharacteristic().setKey("EFFICIENCY");
+    DefaultCharacteristic characteristic = new DefaultCharacteristic().setKey("MEMORY_EFFICIENCY").setParent(parentCharacteristic);
+    when(defaultTechnicalDebtModel.characteristicByKey("MEMORY_EFFICIENCY")).thenReturn(characteristic);
 
     decorator.decorate(resource, context);
 
     verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, ONE_DAY_IN_MINUTES.doubleValue());
     verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, parentCharacteristic, ONE_DAY_IN_MINUTES.doubleValue())));
     verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, characteristic, ONE_DAY_IN_MINUTES.doubleValue())));
-    verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, requirement, ONE_DAY_IN_MINUTES.doubleValue())));
+    verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, ONE_DAY_IN_MINUTES.doubleValue())));
   }
 
   @Test
@@ -190,22 +179,20 @@ public class TechnicalDebtDecoratorTest {
     Issue issue4 = createIssue("rule2", "repo2").setDebt(Duration.create(technicalDebt2));
     when(issuable.issues()).thenReturn(newArrayList(issue1, issue2, issue3, issue4));
 
-    DefaultCharacteristic rootCharacteristic = new DefaultCharacteristic().setKey("rootCharacteristic");
-    DefaultCharacteristic characteristic = new DefaultCharacteristic().setKey("characteristic").setParent(rootCharacteristic);
-    RuleKey ruleKey1 = RuleKey.of("repo1", "rule1");
-    DefaultRequirement requirement1 = new DefaultRequirement().setRuleKey(ruleKey1).setCharacteristic(characteristic);
-    RuleKey ruleKey2 = RuleKey.of("repo2", "rule2");
-    DefaultRequirement requirement2 = new DefaultRequirement().setRuleKey(ruleKey2).setCharacteristic(characteristic);
-
-    when(defaultTechnicalDebtModel.requirementsByRule(ruleKey1)).thenReturn(requirement1);
-    when(defaultTechnicalDebtModel.requirementsByRule(ruleKey2)).thenReturn(requirement2);
-    doReturn(newArrayList(requirement1, requirement2)).when(defaultTechnicalDebtModel).requirements();
+    when(defaultTechnicalDebtModel.characteristicByKey("MEMORY_EFFICIENCY")).thenReturn(
+      new DefaultCharacteristic().setKey("MEMORY_EFFICIENCY").setParent(
+        new DefaultCharacteristic().setKey("EFFICIENCY")
+      ),
+      new DefaultCharacteristic().setKey("MODULARITY").setParent(
+        new DefaultCharacteristic().setKey("REUSABILITY")
+      )
+    );
 
     decorator.decorate(resource, context);
 
     verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 6d * ONE_DAY_IN_MINUTES);
-    verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, requirement1, 2d * ONE_DAY_IN_MINUTES)));
-    verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, requirement2, 4d * ONE_DAY_IN_MINUTES)));
+    verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, 2d * ONE_DAY_IN_MINUTES)));
+    verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey2, 4d * ONE_DAY_IN_MINUTES)));
   }
 
   @Test
@@ -214,21 +201,20 @@ public class TechnicalDebtDecoratorTest {
     Issue issue2 = createIssue("rule1", "repo1").setDebt(Duration.create(ONE_DAY_IN_MINUTES));
     when(issuable.issues()).thenReturn(newArrayList(issue1, issue2));
 
-    DefaultCharacteristic rootCharacteristic = new DefaultCharacteristic().setKey("rootCharacteristic");
-    DefaultCharacteristic characteristic = new DefaultCharacteristic().setKey("characteristic").setParent(rootCharacteristic);
-    RuleKey ruleKey1 = RuleKey.of("repo1", "rule1");
-    DefaultRequirement requirement = new DefaultRequirement().setRuleKey(ruleKey1).setCharacteristic(characteristic);
+    when(defaultTechnicalDebtModel.characteristicByKey("MEMORY_EFFICIENCY")).thenReturn(
+      new DefaultCharacteristic().setKey("MEMORY_EFFICIENCY").setParent(
+        new DefaultCharacteristic().setKey("EFFICIENCY")
+      )
+    );
 
-    when(defaultTechnicalDebtModel.requirementsByRule(ruleKey1)).thenReturn(requirement);
-    doReturn(newArrayList(requirement)).when(defaultTechnicalDebtModel).requirements();
-
-    Measure measure = new Measure().setRequirement(requirement).setValue(5d * ONE_DAY_IN_MINUTES);
+    org.sonar.api.rules.Rule oldRule = org.sonar.api.rules.Rule.create(ruleKey1.repository(), ruleKey1.rule());
+    Measure measure = new RuleMeasure(CoreMetrics.TECHNICAL_DEBT, oldRule, null, null).setValue(5d * ONE_DAY_IN_MINUTES);
     when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(newArrayList(measure));
 
     decorator.decorate(resource, context);
 
     verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 7d * ONE_DAY_IN_MINUTES);
-    verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, requirement, 7d * ONE_DAY_IN_MINUTES)));
+    verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, 7d * ONE_DAY_IN_MINUTES)));
   }
 
   @Test
@@ -265,8 +251,8 @@ public class TechnicalDebtDecoratorTest {
     DecoratorContext context = mock(DecoratorContext.class);
     when(context.getResource()).thenReturn(new Project("foo"));
 
-    DefaultCharacteristic rootCharacteristic = new DefaultCharacteristic().setKey("rootCharacteristic");
-    DefaultCharacteristic characteristic = new DefaultCharacteristic().setKey("characteristic").setParent(rootCharacteristic);
+    DefaultCharacteristic rootCharacteristic = new DefaultCharacteristic().setKey("EFFICIENCY");
+    DefaultCharacteristic characteristic = new DefaultCharacteristic().setKey("MEMORY_EFFICIENCY").setParent(rootCharacteristic);
 
     decorator.saveTechnicalDebt(context, characteristic, 0.0, true);
     verify(context, never()).saveMeasure(any(Measure.class));
@@ -293,34 +279,17 @@ public class TechnicalDebtDecoratorTest {
   class IsCharacteristicMeasure extends ArgumentMatcher<Measure> {
     Metric metric = null;
     Characteristic characteristic = null;
-    Requirement requirement = null;
     Double value = null;
 
-    public IsCharacteristicMeasure(Metric metric, Characteristic characteristic, Requirement requirement, Double value) {
-      this.metric = metric;
-      this.characteristic = characteristic;
-      this.requirement = requirement;
-      this.value = value;
-    }
-
     public IsCharacteristicMeasure(Metric metric, Characteristic characteristic, Double value) {
       this.metric = metric;
       this.characteristic = characteristic;
-      this.requirement = null;
-      this.value = value;
-    }
-
-    public IsCharacteristicMeasure(Metric metric, Requirement requirement, Double value) {
-      this.metric = metric;
-      this.characteristic = null;
-      this.requirement = requirement;
       this.value = value;
     }
 
     public IsCharacteristicMeasure(Metric metric, Double value) {
       this.metric = metric;
       this.characteristic = null;
-      this.requirement = null;
       this.value = value;
     }
 
@@ -332,7 +301,35 @@ public class TechnicalDebtDecoratorTest {
       Measure m = (Measure) o;
       return ObjectUtils.equals(metric, m.getMetric()) &&
         ObjectUtils.equals(characteristic, m.getCharacteristic()) &&
-        ObjectUtils.equals(requirement, m.getRequirement()) &&
+        ObjectUtils.equals(value, m.getValue());
+    }
+
+    @Override
+    public void describeTo(Description description) {
+      description.appendText(ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE));
+    }
+  }
+
+  class IsRuleMeasure extends ArgumentMatcher<RuleMeasure> {
+    Metric metric = null;
+    RuleKey ruleKey = null;
+    Double value = null;
+
+    public IsRuleMeasure(Metric metric, RuleKey ruleKey, Double value) {
+      this.metric = metric;
+      this.ruleKey = ruleKey;
+      this.value = value;
+    }
+
+    @Override
+    public boolean matches(Object o) {
+      if (!(o instanceof RuleMeasure)) {
+        return false;
+      }
+      RuleMeasure m = (RuleMeasure) o;
+      return ObjectUtils.equals(metric, m.getMetric()) &&
+        ObjectUtils.equals(ruleKey.repository(), m.getRule().getRepositoryKey()) &&
+        ObjectUtils.equals(ruleKey.rule(), m.getRule().getKey()) &&
         ObjectUtils.equals(value, m.getValue());
     }
 
index 88ce12563d1d74643e06912c2a99e75d46746339..6e6f000d5116ad54022ece7db0315643c39bb5ba 100644 (file)
@@ -32,7 +32,6 @@ import org.sonar.api.measures.MetricFinder;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.technicaldebt.batch.Characteristic;
-import org.sonar.api.technicaldebt.batch.Requirement;
 import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
 import org.sonar.batch.index.DefaultIndex;
 
@@ -65,8 +64,7 @@ public class DefaultTimeMachine implements TimeMachine {
       MeasureModel model = (MeasureModel) object[0];
       Integer characteristicId = model.getCharacteristicId();
       Characteristic characteristic = techDebtModel.characteristicById(characteristicId);
-      Requirement requirement = techDebtModel.requirementsById(characteristicId);
-      Measure measure = toMeasure(model, metricById.get(model.getMetricId()), characteristic != null ? characteristic : null, requirement != null ? requirement : null);
+      Measure measure = toMeasure(model, metricById.get(model.getMetricId()), characteristic);
       measure.setDate((Date) object[1]);
       result.add(measure);
     }
@@ -154,7 +152,7 @@ public class DefaultTimeMachine implements TimeMachine {
     return result;
   }
 
-  static Measure toMeasure(MeasureModel model, Metric metric, @Nullable Characteristic characteristic, @Nullable Requirement requirement) {
+  static Measure toMeasure(MeasureModel model, Metric metric, @Nullable Characteristic characteristic) {
     // NOTE: measures on rule are not supported
     Measure measure = new Measure(metric);
     measure.setId(model.getId());
@@ -171,7 +169,6 @@ public class DefaultTimeMachine implements TimeMachine {
     measure.setVariation5(model.getVariationValue5());
     measure.setUrl(model.getUrl());
     measure.setCharacteristic(characteristic);
-    measure.setRequirement(requirement);
     measure.setPersonId(model.getPersonId());
     return measure;
   }
diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/DebtModelLoader.java b/sonar-batch/src/main/java/org/sonar/batch/debt/DebtModelLoader.java
deleted file mode 100644 (file)
index 1cbf38d..0000000
+++ /dev/null
@@ -1,103 +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.batch.debt;
-
-import org.sonar.api.BatchComponent;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RuleQuery;
-import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
-import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
-import org.sonar.core.technicaldebt.DefaultTechnicalDebtModel;
-import org.sonar.core.technicaldebt.db.CharacteristicDao;
-import org.sonar.core.technicaldebt.db.CharacteristicDto;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import static com.google.common.collect.Maps.newHashMap;
-
-public class DebtModelLoader implements BatchComponent {
-
-  private final CharacteristicDao dao;
-  private final RuleFinder ruleFinder;
-
-  public DebtModelLoader(CharacteristicDao dao, RuleFinder ruleFinder) {
-    this.dao = dao;
-    this.ruleFinder = ruleFinder;
-  }
-
-  public TechnicalDebtModel load() {
-    DefaultTechnicalDebtModel model = new DefaultTechnicalDebtModel();
-    List<CharacteristicDto> dtos = dao.selectEnabledCharacteristics();
-    Map<Integer, DefaultCharacteristic> characteristicsById = newHashMap();
-
-    addRootCharacteristics(model, dtos, characteristicsById);
-    addCharacteristics(dtos, characteristicsById);
-    addRequirements(dtos, characteristicsById);
-    return model;
-  }
-
-  private void addRootCharacteristics(DefaultTechnicalDebtModel model, List<CharacteristicDto> dtos, Map<Integer, DefaultCharacteristic> characteristicsById) {
-    for (CharacteristicDto dto : dtos) {
-      if (dto.getParentId() == null) {
-        DefaultCharacteristic rootCharacteristic = dto.toCharacteristic(null);
-        model.addRootCharacteristic(rootCharacteristic);
-        characteristicsById.put(dto.getId(), rootCharacteristic);
-      }
-    }
-  }
-
-  private void addCharacteristics(List<CharacteristicDto> dtos, Map<Integer, DefaultCharacteristic> characteristicsById) {
-    for (CharacteristicDto dto : dtos) {
-      if (dto.getParentId() != null && dto.getRuleId() == null) {
-        DefaultCharacteristic parent = characteristicsById.get(dto.getParentId());
-        DefaultCharacteristic characteristic = dto.toCharacteristic(parent);
-        characteristicsById.put(dto.getId(), characteristic);
-      }
-    }
-  }
-
-  private void addRequirements(List<CharacteristicDto> dtos, Map<Integer, DefaultCharacteristic> characteristicsById) {
-    Map<Integer, Rule> rulesById = rulesById(ruleFinder.findAll(RuleQuery.create()));
-    for (CharacteristicDto dto : dtos) {
-      Integer ruleId = dto.getRuleId();
-      if (ruleId != null) {
-        DefaultCharacteristic characteristic = characteristicsById.get(dto.getParentId());
-        DefaultCharacteristic rootCharacteristic = characteristicsById.get(dto.getRootId());
-        Rule rule = rulesById.get(ruleId);
-        RuleKey ruleKey = RuleKey.of(rule.getRepositoryKey(), rule.getKey());
-        dto.toRequirement(ruleKey, characteristic, rootCharacteristic);
-      }
-    }
-  }
-
-  private Map<Integer, Rule> rulesById(Collection<Rule> rules) {
-    Map<Integer, Rule> rulesById = newHashMap();
-    for (Rule rule : rules) {
-      rulesById.put(rule.getId(), rule);
-    }
-    return rulesById;
-  }
-
-}
index fafeade548973137866ca9e942140e560a5b6d14..237bfa3d93b4c4d7ed3167a3402eab216fd5ed83 100644 (file)
@@ -24,7 +24,16 @@ import org.picocontainer.injectors.ProviderAdapter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
+import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
 import org.sonar.api.utils.TimeProfiler;
+import org.sonar.core.technicaldebt.DefaultTechnicalDebtModel;
+import org.sonar.core.technicaldebt.db.CharacteristicDao;
+import org.sonar.core.technicaldebt.db.CharacteristicDto;
+
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.collect.Maps.newHashMap;
 
 public class DebtModelProvider extends ProviderAdapter {
 
@@ -32,12 +41,42 @@ public class DebtModelProvider extends ProviderAdapter {
 
   private TechnicalDebtModel model;
 
-  public TechnicalDebtModel provide(DebtModelLoader loader) {
+  public TechnicalDebtModel provide(CharacteristicDao dao) {
     if (model == null) {
       TimeProfiler profiler = new TimeProfiler(LOG).start("Loading technical debt model");
-      model = loader.load();
+      model = load(dao);
       profiler.stop();
     }
     return model;
   }
+
+  private TechnicalDebtModel load(CharacteristicDao dao) {
+    DefaultTechnicalDebtModel model = new DefaultTechnicalDebtModel();
+    List<CharacteristicDto> dtos = dao.selectCharacteristics();
+    Map<Integer, DefaultCharacteristic> characteristicsById = newHashMap();
+
+    addRootCharacteristics(model, dtos, characteristicsById);
+    addCharacteristics(dtos, characteristicsById);
+    return model;
+  }
+
+  private void addRootCharacteristics(DefaultTechnicalDebtModel model, List<CharacteristicDto> dtos, Map<Integer, DefaultCharacteristic> characteristicsById) {
+    for (CharacteristicDto dto : dtos) {
+      if (dto.getParentId() == null) {
+        DefaultCharacteristic rootCharacteristic = dto.toCharacteristic(null);
+        model.addRootCharacteristic(rootCharacteristic);
+        characteristicsById.put(dto.getId(), rootCharacteristic);
+      }
+    }
+  }
+
+  private void addCharacteristics(List<CharacteristicDto> dtos, Map<Integer, DefaultCharacteristic> characteristicsById) {
+    for (CharacteristicDto dto : dtos) {
+      if (dto.getParentId() != null && dto.getRuleId() == null) {
+        DefaultCharacteristic parent = characteristicsById.get(dto.getParentId());
+        DefaultCharacteristic characteristic = dto.toCharacteristic(parent);
+        characteristicsById.put(dto.getId(), characteristic);
+      }
+    }
+  }
 }
diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/RuleDebtCalculator.java b/sonar-batch/src/main/java/org/sonar/batch/debt/RuleDebtCalculator.java
deleted file mode 100644 (file)
index 5479876..0000000
+++ /dev/null
@@ -1,95 +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.batch.debt;
-
-import com.google.common.base.Objects;
-import org.sonar.api.BatchExtension;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.config.Settings;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.technicaldebt.batch.Requirement;
-import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
-import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement;
-import org.sonar.api.utils.internal.WorkDuration;
-
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-
-/**
- * Computes the remediation cost based on the quality and analysis models.
- */
-public class RuleDebtCalculator implements BatchExtension {
-
-  private final TechnicalDebtModel model;
-  private final Settings settings;
-
-  public RuleDebtCalculator(TechnicalDebtModel model, Settings settings) {
-    this.model = model;
-    this.settings = settings;
-  }
-
-  /**
-   * Calculate the technical debt from a requirement
-   */
-  @CheckForNull
-  public Long calculateTechnicalDebt(RuleKey ruleKey, @Nullable Double effortToFix) {
-    Requirement requirement = model.requirementsByRule(ruleKey);
-    if (requirement != null) {
-      if (requirement.function().equals(DefaultRequirement.CONSTANT_ISSUE) && effortToFix != null) {
-        throw new IllegalArgumentException("Requirement for '" + ruleKey + "' can not use 'Constant/issue' remediation function " +
-          "because this rule does not have a fixed remediation cost.");
-      }
-      return calculateTechnicalDebt(requirement, effortToFix);
-    }
-    return null;
-  }
-
-  private long calculateTechnicalDebt(Requirement requirement, @Nullable Double effortToFix) {
-    long result = 0L;
-
-    int factorValue = requirement.factorValue();
-    if (factorValue > 0) {
-      int effortToFixValue = Objects.firstNonNull(effortToFix, 1).intValue();
-      result = convertValueAndUnitToMinutes(factorValue, requirement.factorUnit()) * effortToFixValue;
-    }
-
-    int offsetValue = requirement.offsetValue();
-    if (offsetValue > 0) {
-      result += convertValueAndUnitToMinutes(offsetValue, requirement.offsetUnit());
-    }
-    return result;
-  }
-
-  private long convertValueAndUnitToMinutes(int value, WorkDuration.UNIT unit){
-    if (WorkDuration.UNIT.DAYS.equals(unit)) {
-      return 60L * value * hoursInDay();
-    } else if (WorkDuration.UNIT.HOURS.equals(unit)) {
-      return 60L * value;
-    } else if (WorkDuration.UNIT.MINUTES.equals(unit)) {
-      return value;
-    }
-    throw new IllegalStateException("Invalid unit : " + unit);
-  }
-
-  private int hoursInDay(){
-    return settings.getInt(CoreProperties.HOURS_IN_DAY);
-  }
-
-}
index d19ad5de7ad8ba9e83f9369175999505c9c0f14b..b204cd6605c4559c1749f35628c270e19dd23885 100644 (file)
  */
 package org.sonar.batch.issue;
 
+import com.google.common.base.Objects;
 import com.google.common.base.Strings;
+import org.sonar.api.batch.rule.ActiveRule;
+import org.sonar.api.batch.rule.ActiveRules;
+import org.sonar.api.batch.rule.Rule;
+import org.sonar.api.batch.rule.Rules;
 import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.profiles.RulesProfile;
 import org.sonar.api.resources.Project;
+import org.sonar.api.rule.RemediationFunction;
 import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.ActiveRule;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.rules.Violation;
 import org.sonar.api.utils.Duration;
 import org.sonar.api.utils.MessageException;
-import org.sonar.batch.debt.RuleDebtCalculator;
 import org.sonar.core.issue.DefaultIssueBuilder;
 
 import javax.annotation.Nullable;
@@ -40,20 +41,18 @@ import javax.annotation.Nullable;
  */
 public class ModuleIssues {
 
-  private final RulesProfile qProfile;
+  private final ActiveRules activeRules;
+  private final Rules rules;
   private final IssueCache cache;
   private final Project project;
   private final IssueFilters filters;
-  private final RuleDebtCalculator technicalDebtCalculator;
-  private final RuleFinder ruleFinder;
 
-  public ModuleIssues(RulesProfile qProfile, IssueCache cache, Project project, IssueFilters filters, RuleDebtCalculator technicalDebtCalculator, RuleFinder ruleFinder) {
-    this.qProfile = qProfile;
+  public ModuleIssues(ActiveRules activeRules, Rules rules, IssueCache cache, Project project, IssueFilters filters) {
+    this.activeRules = activeRules;
+    this.rules = rules;
     this.cache = cache;
     this.project = project;
     this.filters = filters;
-    this.technicalDebtCalculator = technicalDebtCalculator;
-    this.ruleFinder = ruleFinder;
   }
 
   public boolean initAndAddIssue(DefaultIssue issue) {
@@ -78,10 +77,10 @@ public class ModuleIssues {
 
   private boolean initAndAddIssue(DefaultIssue issue, @Nullable Violation violation) {
     RuleKey ruleKey = issue.ruleKey();
-    Rule rule = ruleFinder.findByKey(ruleKey);
+    Rule rule = rules.find(ruleKey);
     validateRule(issue, rule);
-    ActiveRule activeRule = qProfile.getActiveRule(ruleKey.repository(), ruleKey.rule());
-    if (activeRule == null || activeRule.getRule() == null) {
+    ActiveRule activeRule = activeRules.find(ruleKey);
+    if (activeRule == null) {
       // rule does not exist or is not enabled -> ignore the issue
       return false;
     }
@@ -93,27 +92,48 @@ public class ModuleIssues {
     return false;
   }
 
-  private void validateRule(DefaultIssue issue, Rule rule){
+  private void validateRule(DefaultIssue issue, Rule rule) {
     RuleKey ruleKey = issue.ruleKey();
     if (rule == null) {
       throw MessageException.of(String.format("The rule '%s' does not exist.", ruleKey));
     }
-    if (Strings.isNullOrEmpty(rule.getName()) && Strings.isNullOrEmpty(issue.message())) {
+    if (Strings.isNullOrEmpty(rule.name()) && Strings.isNullOrEmpty(issue.message())) {
       throw MessageException.of(String.format("The rule '%s' has no name and the related issue has no message.", ruleKey));
     }
   }
 
-  private void updateIssue(DefaultIssue issue, Rule rule, ActiveRule activeRule ){
+  private void updateIssue(DefaultIssue issue, Rule rule, ActiveRule activeRule{
     if (Strings.isNullOrEmpty(issue.message())) {
-      issue.setMessage(rule.getName());
+      issue.setMessage(rule.name());
     }
     issue.setCreationDate(project.getAnalysisDate());
     issue.setUpdateDate(project.getAnalysisDate());
     if (issue.severity() == null) {
-      issue.setSeverity(activeRule.getSeverity().name());
+      issue.setSeverity(activeRule.severity());
     }
-    Long debt = technicalDebtCalculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix());
-    issue.setDebt(debt != null ? Duration.create(debt) : null);
+    if (rule.characteristic() != null) {
+      issue.setDebt(calculateDebt(rule, issue.effortToFix()));
+    }
+  }
+
+  private Duration calculateDebt(Rule rule, @Nullable Double effortToFix) {
+    if (RemediationFunction.CONSTANT_ISSUE.equals(rule.function()) && effortToFix != null) {
+      throw new IllegalArgumentException("Rule '" + rule.key() + "' can not use 'Constant/issue' remediation function " +
+        "because this rule does not have a fixed remediation cost.");
+    }
+    Duration result = Duration.create(0);
+    Duration factor = rule.factor();
+    Duration offset = rule.offset();
+
+    if (factor != null) {
+      int effortToFixValue = Objects.firstNonNull(effortToFix, 1).intValue();
+      result = rule.factor().multiply(effortToFixValue);
+    }
+
+    if (offset != null) {
+      result = result.add(offset);
+    }
+    return result;
   }
 
 }
diff --git a/sonar-batch/src/main/java/org/sonar/batch/rule/RulesProvider.java b/sonar-batch/src/main/java/org/sonar/batch/rule/RulesProvider.java
new file mode 100644 (file)
index 0000000..c36b70c
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * 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.batch.rule;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import org.picocontainer.injectors.ProviderAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.rule.Rules;
+import org.sonar.api.batch.rule.internal.NewRule;
+import org.sonar.api.batch.rule.internal.RulesBuilder;
+import org.sonar.api.rule.RemediationFunction;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.technicaldebt.batch.Characteristic;
+import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
+import org.sonar.api.utils.Durations;
+import org.sonar.api.utils.TimeProfiler;
+import org.sonar.core.rule.RuleDao;
+import org.sonar.core.rule.RuleDto;
+import org.sonar.core.rule.RuleParamDto;
+
+import javax.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Loads all enabled and non manual rules
+ */
+public class RulesProvider extends ProviderAdapter {
+
+  private static final Logger LOG = LoggerFactory.getLogger(RulesProvider.class);
+
+  private Rules singleton = null;
+
+  public Rules provide(RuleDao ruleDao, TechnicalDebtModel debtModel, Durations durations) {
+    if (singleton == null) {
+      TimeProfiler profiler = new TimeProfiler(LOG).start("Loading rules");
+      singleton = load(ruleDao, debtModel, durations);
+      profiler.stop();
+    }
+    return singleton;
+  }
+
+  private Rules load(RuleDao ruleDao, TechnicalDebtModel debtModel, Durations durations) {
+    RulesBuilder rulesBuilder = new RulesBuilder();
+
+    List<RuleParamDto> ruleParamDtos = ruleDao.selectParameters();
+    ListMultimap<Integer, RuleParamDto> paramDtosByRuleId = ArrayListMultimap.create();
+    for (RuleParamDto dto : ruleParamDtos) {
+      paramDtosByRuleId.put(dto.getRuleId(), dto);
+    }
+    for (RuleDto ruleDto : ruleDao.selectEnablesAndNonManual()) {
+      RuleKey ruleKey = RuleKey.of(ruleDto.getRepositoryKey(), ruleDto.getRuleKey());
+      NewRule newRule = rulesBuilder.add(ruleKey)
+        .setId(ruleDto.getId())
+        .setName(ruleDto.getName())
+        .setSeverity(ruleDto.getSeverityString())
+        .setDescription(ruleDto.getDescription())
+        .setStatus(RuleStatus.valueOf(ruleDto.getStatus()));
+      // TODO should we set metadata ?
+      if (ruleDto.getCharacteristicId() != null) {
+        Characteristic characteristic = characteristic(ruleDto.getCharacteristicId(), ruleKey, debtModel);
+        updateRuleDebtDefinitions(newRule, ruleKey, characteristic, ruleDto.getRemediationFunction(), ruleDto.getRemediationFactor(), ruleDto.getRemediationOffset(), durations);
+      } else if (ruleDto.getDefaultCharacteristicId() != null) {
+        Characteristic characteristic = characteristic(ruleDto.getDefaultCharacteristicId(), ruleKey, debtModel);
+        updateRuleDebtDefinitions(newRule, ruleKey, characteristic, ruleDto.getDefaultRemediationFunction(), ruleDto.getDefaultRemediationFactor(),
+          ruleDto.getDefaultRemediationOffset(), durations);
+      }
+      for (RuleParamDto ruleParamDto : paramDtosByRuleId.get(ruleDto.getId())) {
+        newRule.addParam(ruleParamDto.getName())
+          .setDescription(ruleParamDto.getDescription());
+      }
+    }
+    return rulesBuilder.build();
+  }
+
+  private void updateRuleDebtDefinitions(NewRule newRule, RuleKey ruleKey, Characteristic characteristic, @Nullable String function,
+                                         @Nullable String factor, @Nullable String offset,
+                                         Durations durations) {
+    newRule.setCharacteristic(characteristic.key());
+    newRule.setFunction(function(function, ruleKey));
+    newRule.setFactor(factor != null ? durations.decode(factor) : null);
+    newRule.setOffset(offset != null ? durations.decode(offset) : null);
+  }
+
+  private Characteristic characteristic(Integer characteristicId, RuleKey ruleKey, TechnicalDebtModel debtModel) {
+    Characteristic characteristic = debtModel.characteristicById(characteristicId);
+    if (characteristic == null) {
+      throw new IllegalStateException(String.format("Characteristic id '%s' on rule '%s' has not been found", characteristicId, ruleKey));
+    }
+    return characteristic;
+  }
+
+  private RemediationFunction function(@Nullable String function, RuleKey ruleKey) {
+    if (function == null) {
+      throw new IllegalStateException(String.format("Remediation function should not be null on rule '%s'", ruleKey));
+    }
+    return RemediationFunction.valueOf(function);
+  }
+}
index 4d3f6876bddefa09b611d57cc5dedca44cb3bb64..5e0446c8ae75725714186123c7be09f870979d28 100644 (file)
@@ -37,15 +37,14 @@ import org.sonar.batch.ProjectConfigurator;
 import org.sonar.batch.ProjectTree;
 import org.sonar.batch.bootstrap.*;
 import org.sonar.batch.components.PeriodsDefinition;
-import org.sonar.batch.debt.DebtModelLoader;
 import org.sonar.batch.debt.DebtModelProvider;
 import org.sonar.batch.debt.IssueChangelogDebtCalculator;
-import org.sonar.batch.debt.RuleDebtCalculator;
 import org.sonar.batch.index.*;
 import org.sonar.batch.issue.*;
 import org.sonar.batch.phases.GraphPersister;
 import org.sonar.batch.profiling.PhasesSumUpTimeProfiler;
 import org.sonar.batch.qualitygate.ProjectAlerts;
+import org.sonar.batch.rule.RulesProvider;
 import org.sonar.batch.scan.filesystem.InputFileCache;
 import org.sonar.batch.scan.maven.FakeMavenPluginExecutor;
 import org.sonar.batch.scan.maven.MavenPluginExecutor;
@@ -156,17 +155,19 @@ public class ProjectScanContainer extends ComponentContainer {
       SymbolizableBuilder.class,
 
       // technical debt
-      DebtModelLoader.class,
-      RuleDebtCalculator.class,
       new DebtModelProvider(),
 
+      // quality gates
+      ProjectAlerts.class,
+
+      // rules
+      new RulesProvider(),
+
       // Differential periods
       PeriodsDefinition.class,
 
-      ProjectSettingsReady.class,
-
-      // quality gates
-      ProjectAlerts.class);
+      ProjectSettingsReady.class
+    );
   }
 
   private void fixMavenExecutor() {
diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/DebtModelLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/DebtModelLoaderTest.java
deleted file mode 100644 (file)
index 2de08c8..0000000
+++ /dev/null
@@ -1,117 +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.batch.debt;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RuleQuery;
-import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
-import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement;
-import org.sonar.api.utils.internal.WorkDuration;
-import org.sonar.core.technicaldebt.DefaultTechnicalDebtModel;
-import org.sonar.core.technicaldebt.db.CharacteristicDao;
-import org.sonar.core.technicaldebt.db.CharacteristicDto;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.when;
-
-@RunWith(MockitoJUnitRunner.class)
-public class DebtModelLoaderTest {
-
-  @Mock
-  CharacteristicDao dao;
-
-  @Mock
-  RuleFinder ruleFinder;
-
-  DebtModelLoader loader;
-
-  @Before
-  public void before() {
-    loader = new DebtModelLoader(dao, ruleFinder);
-  }
-
-  @Test
-  public void find_all() throws Exception {
-    CharacteristicDto rootCharacteristicDto = new CharacteristicDto()
-      .setId(1)
-      .setKey("MEMORY_EFFICIENCY")
-      .setName("Memory use");
-
-    CharacteristicDto characteristicDto = new CharacteristicDto()
-      .setId(2)
-      .setKey("EFFICIENCY")
-      .setName("Efficiency")
-      .setParentId(1);
-
-    CharacteristicDto requirementDto = new CharacteristicDto()
-      .setId(3)
-      .setParentId(2)
-      .setRuleId(100)
-      .setFunction("linear")
-      .setFactorValue(2d)
-      .setFactorUnit(CharacteristicDto.DAYS)
-      .setOffsetValue(0d)
-      .setOffsetUnit(CharacteristicDto.MINUTES);
-
-    RuleKey ruleKey = RuleKey.of("checkstyle", "Regexp");
-    Rule rule = Rule.create(ruleKey.repository(), ruleKey.rule());
-    rule.setId(100);
-    when(ruleFinder.findAll(any(RuleQuery.class))).thenReturn(newArrayList(rule));
-    when(dao.selectEnabledCharacteristics()).thenReturn(newArrayList(rootCharacteristicDto, characteristicDto, requirementDto));
-
-    DefaultTechnicalDebtModel result = (DefaultTechnicalDebtModel) loader.load();
-    assertThat(result.rootCharacteristics()).hasSize(1);
-
-    DefaultCharacteristic rootCharacteristic = result.characteristicByKey("MEMORY_EFFICIENCY");
-    assertThat(rootCharacteristic.key()).isEqualTo("MEMORY_EFFICIENCY");
-    assertThat(rootCharacteristic.name()).isEqualTo("Memory use");
-    assertThat(rootCharacteristic.parent()).isNull();
-    assertThat(rootCharacteristic.requirements()).isEmpty();
-    assertThat(rootCharacteristic.children()).hasSize(1);
-    assertThat(rootCharacteristic.children().get(0).key()).isEqualTo("EFFICIENCY");
-
-    DefaultCharacteristic characteristic = result.characteristicByKey("EFFICIENCY");
-    assertThat(characteristic.key()).isEqualTo("EFFICIENCY");
-    assertThat(characteristic.name()).isEqualTo("Efficiency");
-    assertThat(characteristic.parent().key()).isEqualTo("MEMORY_EFFICIENCY");
-    assertThat(characteristic.children()).isEmpty();
-    assertThat(characteristic.requirements()).hasSize(1);
-    assertThat(characteristic.requirements().get(0).ruleKey()).isEqualTo(ruleKey);
-
-    DefaultRequirement requirement = result.requirementsByRule(ruleKey);
-    assertThat(requirement.ruleKey()).isEqualTo(ruleKey);
-    assertThat(requirement.function()).isEqualTo("linear");
-    assertThat(requirement.factorValue()).isEqualTo(2);
-    assertThat(requirement.factorUnit()).isEqualTo(WorkDuration.UNIT.DAYS);
-    assertThat(requirement.offsetValue()).isEqualTo(0);
-    assertThat(requirement.offsetUnit()).isEqualTo(WorkDuration.UNIT.MINUTES);
-  }
-
-}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/DebtModelProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/DebtModelProviderTest.java
new file mode 100644 (file)
index 0000000..50e08fc
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * 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.batch.debt;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
+import org.sonar.core.technicaldebt.DefaultTechnicalDebtModel;
+import org.sonar.core.technicaldebt.db.CharacteristicDao;
+import org.sonar.core.technicaldebt.db.CharacteristicDto;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DebtModelProviderTest {
+
+  @Mock
+  CharacteristicDao dao;
+
+  DebtModelProvider provider;
+
+
+  @Before
+  public void before() {
+    provider = new DebtModelProvider();
+  }
+
+  @Test
+  public void find_all() throws Exception {
+    CharacteristicDto rootCharacteristicDto = new CharacteristicDto()
+      .setId(1)
+      .setKey("MEMORY_EFFICIENCY")
+      .setName("Memory use");
+
+    CharacteristicDto characteristicDto = new CharacteristicDto()
+      .setId(2)
+      .setKey("EFFICIENCY")
+      .setName("Efficiency")
+      .setParentId(1);
+
+    CharacteristicDto requirementDto = new CharacteristicDto()
+      .setId(3)
+      .setParentId(2)
+      .setRuleId(100)
+      .setFunction("linear")
+      .setFactorValue(2d)
+      .setFactorUnit(CharacteristicDto.DAYS)
+      .setOffsetValue(0d)
+      .setOffsetUnit(CharacteristicDto.MINUTES);
+
+    RuleKey ruleKey = RuleKey.of("checkstyle", "Regexp");
+    Rule rule = Rule.create(ruleKey.repository(), ruleKey.rule());
+    rule.setId(100);
+    when(dao.selectCharacteristics()).thenReturn(newArrayList(rootCharacteristicDto, characteristicDto, requirementDto));
+
+    DefaultTechnicalDebtModel result = (DefaultTechnicalDebtModel) provider.provide(dao);
+    assertThat(result.rootCharacteristics()).hasSize(1);
+
+    DefaultCharacteristic rootCharacteristic = result.characteristicByKey("MEMORY_EFFICIENCY");
+    assertThat(rootCharacteristic.key()).isEqualTo("MEMORY_EFFICIENCY");
+    assertThat(rootCharacteristic.name()).isEqualTo("Memory use");
+    assertThat(rootCharacteristic.parent()).isNull();
+    assertThat(rootCharacteristic.children()).hasSize(1);
+    assertThat(rootCharacteristic.children().get(0).key()).isEqualTo("EFFICIENCY");
+
+    DefaultCharacteristic characteristic = result.characteristicByKey("EFFICIENCY");
+    assertThat(characteristic.key()).isEqualTo("EFFICIENCY");
+    assertThat(characteristic.name()).isEqualTo("Efficiency");
+    assertThat(characteristic.parent().key()).isEqualTo("MEMORY_EFFICIENCY");
+    assertThat(characteristic.children()).isEmpty();
+  }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/RuleDebtCalculatorTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/RuleDebtCalculatorTest.java
deleted file mode 100644 (file)
index c53cdcd..0000000
+++ /dev/null
@@ -1,145 +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.batch.debt;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.config.Settings;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
-import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement;
-import org.sonar.api.utils.internal.WorkDuration;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.fest.assertions.Fail.fail;
-import static org.mockito.Mockito.when;
-
-@RunWith(MockitoJUnitRunner.class)
-public class RuleDebtCalculatorTest {
-
-  private static final int HOURS_IN_DAY = 8;
-
-  @Mock
-  TechnicalDebtModel model;
-
-  RuleDebtCalculator calculator;
-
-  @Before
-  public void before() {
-    calculator = new RuleDebtCalculator(model, new Settings().setProperty(CoreProperties.HOURS_IN_DAY, 8));
-  }
-
-  @Test
-  public void calculate_technical_debt() throws Exception {
-    RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
-    DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey);
-
-    DefaultRequirement requirement = new DefaultRequirement()
-      .setFunction("constant_issue")
-      .setFactorValue(10)
-      .setFactorUnit(WorkDuration.UNIT.MINUTES)
-      .setOffsetValue(5)
-      .setOffsetUnit(WorkDuration.UNIT.MINUTES);
-    when(model.requirementsByRule(ruleKey)).thenReturn(requirement);
-
-    assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isEqualTo(15L);
-  }
-
-  @Test
-  public void calculate_technical_debt_with_effort_to_fix() throws Exception {
-    RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
-    DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey).setEffortToFix(2d);
-
-    DefaultRequirement requirement = new DefaultRequirement()
-      .setFunction("linear_offset")
-      .setFactorValue(10)
-      .setFactorUnit(WorkDuration.UNIT.MINUTES)
-      .setOffsetValue(5)
-      .setOffsetUnit(WorkDuration.UNIT.MINUTES);
-    when(model.requirementsByRule(ruleKey)).thenReturn(requirement);
-
-    assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isEqualTo(((10 * 2) + 5));
-  }
-
-  @Test
-  public void calculate_technical_debt_with_no_offset() throws Exception {
-    RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
-    DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey).setEffortToFix(2d);
-
-    DefaultRequirement requirement = new DefaultRequirement()
-      .setFunction("linear")
-      .setFactorValue(10)
-      .setFactorUnit(WorkDuration.UNIT.HOURS);
-    when(model.requirementsByRule(ruleKey)).thenReturn(requirement);
-
-    assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isEqualTo((10 * 2) * 60);
-  }
-
-  @Test
-  public void calculate_technical_debt_with_no_factor() throws Exception {
-    RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
-    DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey);
-
-    DefaultRequirement requirement = new DefaultRequirement()
-      .setFunction("constant_issue")
-      .setOffsetValue(5)
-      .setOffsetUnit(WorkDuration.UNIT.DAYS);
-
-    when(model.requirementsByRule(ruleKey)).thenReturn(requirement);
-
-    assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isEqualTo(5 * HOURS_IN_DAY * 60);
-  }
-
-  @Test
-  public void no_technical_debt_if_requirement_not_found() throws Exception {
-    RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
-    DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey);
-    when(model.requirementsByRule(ruleKey)).thenReturn(null);
-
-    assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isNull();
-  }
-
-  @Test
-  public void fail_to_calculate_technical_debt_on_constant_issue_function_with_effort_to_fix() throws Exception {
-    RuleKey ruleKey = RuleKey.of("squid", "AvoidCycle");
-    DefaultIssue issue = new DefaultIssue().setKey("ABCDE").setRuleKey(ruleKey).setEffortToFix(2d);
-
-    DefaultRequirement requirement = new DefaultRequirement()
-      .setFunction("constant_issue")
-      .setOffsetValue(5)
-      .setOffsetUnit(WorkDuration.UNIT.MINUTES);
-    when(model.requirementsByRule(ruleKey)).thenReturn(requirement);
-
-    try {
-      assertThat(calculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).isEqualTo(15);
-      fail();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(IllegalArgumentException.class)
-        .hasMessage("Requirement for 'squid:AvoidCycle' can not use 'Constant/issue' remediation function because this rule does not have a fixed remediation cost.");
-    }
-  }
-
-}
-
diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/TechnicalDebtModelProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/TechnicalDebtModelProviderTest.java
deleted file mode 100644 (file)
index 05747f8..0000000
+++ /dev/null
@@ -1,60 +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.batch.debt;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Mockito.*;
-
-@RunWith(MockitoJUnitRunner.class)
-public class TechnicalDebtModelProviderTest {
-
-  @Mock
-  DebtModelLoader loader;
-
-  @Test
-  public void load_model() {
-    TechnicalDebtModel model = mock(TechnicalDebtModel.class);
-    when(loader.load()).thenReturn(model);
-
-    DebtModelProvider provider = new DebtModelProvider();
-    TechnicalDebtModel result = provider.provide(loader);
-    assertThat(result).isNotNull();
-  }
-
-  @Test
-  public void load_model_only_once() {
-    TechnicalDebtModel model = mock(TechnicalDebtModel.class);
-    when(loader.load()).thenReturn(model);
-
-    DebtModelProvider provider = new DebtModelProvider();
-    provider.provide(loader);
-    verify(loader).load();
-
-    provider.provide(loader);
-    verifyZeroInteractions(loader);
-  }
-}
index 1150211081e81b5325bd7eff2168f924c42991b7..b8c4d1efbf13b49f38ed9940a4fe392ee8377aa8 100644 (file)
@@ -26,17 +26,19 @@ import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
+import org.sonar.api.batch.rule.internal.RulesBuilder;
 import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.profiles.RulesProfile;
 import org.sonar.api.resources.JavaFile;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
+import org.sonar.api.rule.RemediationFunction;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.Severity;
-import org.sonar.api.rules.*;
+import org.sonar.api.rules.RulePriority;
+import org.sonar.api.rules.Violation;
 import org.sonar.api.utils.Duration;
 import org.sonar.api.utils.MessageException;
-import org.sonar.batch.debt.RuleDebtCalculator;
 
 import java.util.Calendar;
 import java.util.Date;
@@ -44,7 +46,6 @@ import java.util.Date;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.Fail.fail;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.*;
 
@@ -52,25 +53,19 @@ import static org.mockito.Mockito.*;
 public class ModuleIssuesTest {
 
   static final RuleKey SQUID_RULE_KEY = RuleKey.of("squid", "AvoidCycle");
-  static final Rule SQUID_RULE = Rule.create("squid", "AvoidCycle").setName("Avoid Cycle");
+  static final String SQUID_RULE_NAME = "Avoid Cycle";
 
   @Mock
   IssueCache cache;
 
-  @Mock
-  RulesProfile qProfile;
-
   @Mock
   Project project;
 
   @Mock
   IssueFilters filters;
 
-  @Mock
-  RuleDebtCalculator technicalDebtCalculator;
-
-  @Mock
-  RuleFinder ruleFinder;
+  ActiveRulesBuilder activeRulesBuilder = new ActiveRulesBuilder();
+  RulesBuilder ruleBuilder = new RulesBuilder();
 
   ModuleIssues moduleIssues;
 
@@ -78,13 +73,11 @@ public class ModuleIssuesTest {
   public void setUp() {
     when(project.getAnalysisDate()).thenReturn(new Date());
     when(project.getEffectiveKey()).thenReturn("org.apache:struts-core");
-
-    moduleIssues = new ModuleIssues(qProfile, cache, project, filters, technicalDebtCalculator, ruleFinder);
   }
 
   @Test
   public void fail_on_unknown_rule() throws Exception {
-    when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(null);
+    initModuleIssues();
     DefaultIssue issue = new DefaultIssue().setRuleKey(SQUID_RULE_KEY);
 
     try {
@@ -99,7 +92,8 @@ public class ModuleIssuesTest {
 
   @Test
   public void fail_if_rule_has_no_name_and_issue_has_no_message() throws Exception {
-    when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(Rule.create("squid", "AvoidCycle"));
+    ruleBuilder.add(RuleKey.of("squid", "AvoidCycle"));
+    initModuleIssues();
     DefaultIssue issue = new DefaultIssue().setRuleKey(SQUID_RULE_KEY).setMessage("");
 
     try {
@@ -114,8 +108,8 @@ public class ModuleIssuesTest {
 
   @Test
   public void ignore_null_active_rule() throws Exception {
-    when(qProfile.getActiveRule(anyString(), anyString())).thenReturn(null);
-    when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(SQUID_RULE);
+    ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME);
+    initModuleIssues();
 
     DefaultIssue issue = new DefaultIssue().setRuleKey(SQUID_RULE_KEY);
     boolean added = moduleIssues.initAndAddIssue(issue);
@@ -126,10 +120,9 @@ public class ModuleIssuesTest {
 
   @Test
   public void ignore_null_rule_of_active_rule() throws Exception {
-    ActiveRule activeRule = mock(ActiveRule.class);
-    when(activeRule.getRule()).thenReturn(null);
-    when(qProfile.getActiveRule(anyString(), anyString())).thenReturn(activeRule);
-    when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(SQUID_RULE);
+    ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME);
+    activeRulesBuilder.activate(SQUID_RULE_KEY);
+    initModuleIssues();
 
     DefaultIssue issue = new DefaultIssue().setRuleKey(SQUID_RULE_KEY);
     boolean added = moduleIssues.initAndAddIssue(issue);
@@ -140,12 +133,9 @@ public class ModuleIssuesTest {
 
   @Test
   public void add_issue_to_cache() throws Exception {
-    Rule rule = SQUID_RULE;
-    ActiveRule activeRule = mock(ActiveRule.class);
-    when(activeRule.getRule()).thenReturn(rule);
-    when(activeRule.getSeverity()).thenReturn(RulePriority.INFO);
-    when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule);
-    when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(rule);
+    ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME);
+    activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO);
+    initModuleIssues();
 
     Date analysisDate = new Date();
     when(project.getAnalysisDate()).thenReturn(analysisDate);
@@ -166,13 +156,10 @@ public class ModuleIssuesTest {
   }
 
   @Test
-  public void use_severity_from_active_rule_if_no_severity() throws Exception {
-    Rule rule = SQUID_RULE;
-    ActiveRule activeRule = mock(ActiveRule.class);
-    when(activeRule.getRule()).thenReturn(rule);
-    when(activeRule.getSeverity()).thenReturn(RulePriority.INFO);
-    when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule);
-    when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(rule);
+  public void use_severity_from_active_rule_if_no_severity_on_issue() throws Exception {
+    ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME);
+    activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO);
+    initModuleIssues();
 
     Date analysisDate = new Date();
     when(project.getAnalysisDate()).thenReturn(analysisDate);
@@ -189,12 +176,9 @@ public class ModuleIssuesTest {
 
   @Test
   public void use_rule_name_if_no_message() throws Exception {
-    Rule rule = SQUID_RULE;
-    ActiveRule activeRule = mock(ActiveRule.class);
-    when(activeRule.getRule()).thenReturn(rule);
-    when(activeRule.getSeverity()).thenReturn(RulePriority.INFO);
-    when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule);
-    when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(rule);
+    ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME);
+    activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO);
+    initModuleIssues();
 
     Date analysisDate = new Date();
     when(project.getAnalysisDate()).thenReturn(analysisDate);
@@ -216,18 +200,17 @@ public class ModuleIssuesTest {
 
   @Test
   public void add_deprecated_violation() throws Exception {
-    Rule rule = SQUID_RULE;
+    ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME);
+    activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO);
+    initModuleIssues();
+
+    org.sonar.api.rules.Rule rule = org.sonar.api.rules.Rule.create("squid", "AvoidCycle", "Avoid Cycle");
     Resource resource = new JavaFile("org.struts.Action").setEffectiveKey("struts:org.struts.Action");
     Violation violation = new Violation(rule, resource);
     violation.setLineId(42);
     violation.setSeverity(RulePriority.CRITICAL);
     violation.setMessage("the message");
 
-    ActiveRule activeRule = mock(ActiveRule.class);
-    when(activeRule.getRule()).thenReturn(rule);
-    when(activeRule.getSeverity()).thenReturn(RulePriority.INFO);
-    when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule);
-    when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(rule);
     when(filters.accept(any(DefaultIssue.class), eq(violation))).thenReturn(true);
 
     boolean added = moduleIssues.initAndAddViolation(violation);
@@ -241,17 +224,14 @@ public class ModuleIssuesTest {
     assertThat(issue.message()).isEqualTo("the message");
     assertThat(issue.key()).isNotEmpty();
     assertThat(issue.ruleKey().toString()).isEqualTo("squid:AvoidCycle");
-    assertThat(issue.componentKey().toString()).isEqualTo("struts:org.struts.Action");
+    assertThat(issue.componentKey()).isEqualTo("struts:org.struts.Action");
   }
 
   @Test
   public void filter_issue() throws Exception {
-    Rule rule = SQUID_RULE;
-    ActiveRule activeRule = mock(ActiveRule.class);
-    when(activeRule.getRule()).thenReturn(rule);
-    when(activeRule.getSeverity()).thenReturn(RulePriority.INFO);
-    when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule);
-    when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(rule);
+    ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME);
+    activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO);
+    initModuleIssues();
 
     DefaultIssue issue = new DefaultIssue()
       .setKey("ABCDE")
@@ -267,13 +247,14 @@ public class ModuleIssuesTest {
   }
 
   @Test
-  public void set_remediation_cost() throws Exception {
-    Rule rule = SQUID_RULE;
-    ActiveRule activeRule = mock(ActiveRule.class);
-    when(activeRule.getRule()).thenReturn(rule);
-    when(activeRule.getSeverity()).thenReturn(RulePriority.INFO);
-    when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule);
-    when(ruleFinder.findByKey(SQUID_RULE_KEY)).thenReturn(rule);
+  public void set_debt_with_linear_function() throws Exception {
+    ruleBuilder.add(SQUID_RULE_KEY)
+      .setName(SQUID_RULE_NAME)
+      .setCharacteristic("COMPILER_RELATED_PORTABILITY")
+      .setFunction(RemediationFunction.LINEAR)
+      .setFactor(Duration.create(10L));
+    activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO);
+    initModuleIssues();
 
     Date analysisDate = new Date();
     when(project.getAnalysisDate()).thenReturn(analysisDate);
@@ -281,17 +262,104 @@ public class ModuleIssuesTest {
     DefaultIssue issue = new DefaultIssue()
       .setKey("ABCDE")
       .setRuleKey(SQUID_RULE_KEY)
-      .setSeverity(Severity.CRITICAL);
+      .setSeverity(Severity.CRITICAL)
+      .setEffortToFix(2d);
+
+    when(filters.accept(issue, null)).thenReturn(true);
+    moduleIssues.initAndAddIssue(issue);
+
+    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(cache).put(argument.capture());
+    assertThat(argument.getValue().debt()).isEqualTo(Duration.create(20L));
+  }
+
+  @Test
+  public void set_debt_with_linear_with_offset_function() throws Exception {
+    ruleBuilder.add(SQUID_RULE_KEY)
+      .setName(SQUID_RULE_NAME)
+      .setCharacteristic("COMPILER_RELATED_PORTABILITY")
+      .setFunction(RemediationFunction.LINEAR_OFFSET)
+      .setFactor(Duration.create(10L))
+      .setOffset(Duration.create(25L));
+    activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO);
+    initModuleIssues();
+
+    Date analysisDate = new Date();
+    when(project.getAnalysisDate()).thenReturn(analysisDate);
+
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setRuleKey(SQUID_RULE_KEY)
+      .setSeverity(Severity.CRITICAL)
+      .setEffortToFix(2d);
 
-    Long debt = 10L;
-    when(technicalDebtCalculator.calculateTechnicalDebt(issue.ruleKey(), issue.effortToFix())).thenReturn(debt);
     when(filters.accept(issue, null)).thenReturn(true);
+    moduleIssues.initAndAddIssue(issue);
+
+    ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(cache).put(argument.capture());
+    assertThat(argument.getValue().debt()).isEqualTo(Duration.create(45L));
+  }
+
+  @Test
+  public void set_debt_with_constant_issue_function() throws Exception {
+    ruleBuilder.add(SQUID_RULE_KEY)
+      .setName(SQUID_RULE_NAME)
+      .setCharacteristic("COMPILER_RELATED_PORTABILITY")
+      .setFunction(RemediationFunction.CONSTANT_ISSUE)
+      .setOffset(Duration.create(10L));
+    activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO);
+    initModuleIssues();
+
+    Date analysisDate = new Date();
+    when(project.getAnalysisDate()).thenReturn(analysisDate);
 
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setRuleKey(SQUID_RULE_KEY)
+      .setSeverity(Severity.CRITICAL)
+      .setEffortToFix(null);
+
+    when(filters.accept(issue, null)).thenReturn(true);
     moduleIssues.initAndAddIssue(issue);
 
     ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
     verify(cache).put(argument.capture());
-    assertThat(argument.getValue().debt()).isEqualTo(Duration.create(debt));
+    assertThat(argument.getValue().debt()).isEqualTo(Duration.create(10L));
+  }
+
+  @Test
+  public void fail_to_set_debt_with_constant_issue_function_when_effort_to_fix_is_set() throws Exception {
+    ruleBuilder.add(SQUID_RULE_KEY)
+      .setName(SQUID_RULE_NAME)
+      .setCharacteristic("COMPILER_RELATED_PORTABILITY")
+      .setFunction(RemediationFunction.CONSTANT_ISSUE)
+      .setOffset(Duration.create(25L));
+    activeRulesBuilder.activate(SQUID_RULE_KEY).setSeverity(Severity.INFO);
+    initModuleIssues();
+
+    DefaultIssue issue = new DefaultIssue()
+      .setKey("ABCDE")
+      .setRuleKey(SQUID_RULE_KEY)
+      .setSeverity(Severity.CRITICAL)
+      .setEffortToFix(2d);
+
+    when(filters.accept(issue, null)).thenReturn(true);
+
+    try {
+      moduleIssues.initAndAddIssue(issue);
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Rule 'squid:AvoidCycle' can not use 'Constant/issue' remediation function because this rule does not have a fixed remediation cost.");
+    }
+  }
+
+  /**
+   * Every rules and active rules has to be added in builders before creating ModuleIssues
+   */
+  private void initModuleIssues() {
+    moduleIssues = new ModuleIssues(activeRulesBuilder.build(), ruleBuilder.build(), cache, project, filters);
   }
 
 }
diff --git a/sonar-batch/src/test/java/org/sonar/batch/rule/RulesProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/rule/RulesProviderTest.java
new file mode 100644 (file)
index 0000000..dc5d9b9
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * 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.batch.rule;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.batch.rule.Rule;
+import org.sonar.api.batch.rule.RuleParam;
+import org.sonar.api.batch.rule.Rules;
+import org.sonar.api.config.Settings;
+import org.sonar.api.rule.RemediationFunction;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
+import org.sonar.api.utils.Duration;
+import org.sonar.api.utils.Durations;
+import org.sonar.core.persistence.AbstractDaoTestCase;
+import org.sonar.core.rule.RuleDao;
+import org.sonar.core.technicaldebt.DefaultTechnicalDebtModel;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+
+@RunWith(MockitoJUnitRunner.class)
+public class RulesProviderTest extends AbstractDaoTestCase {
+
+  @Mock
+  Durations durations;
+
+  RuleDao ruleDao;
+
+  DefaultTechnicalDebtModel debtModel;
+
+  RulesProvider provider;
+
+  @Before
+  public void setUp() throws Exception {
+    debtModel = new DefaultTechnicalDebtModel();
+    debtModel.addRootCharacteristic(new DefaultCharacteristic()
+      .setId(101)
+      .setKey("EFFICIENCY")
+      .setName("Efficiency")
+      .setParent(new DefaultCharacteristic()
+        .setId(100)
+        .setKey("MEMORY_EFFICIENCY")
+        .setName("Memory use")));
+    debtModel.addRootCharacteristic(new DefaultCharacteristic()
+      .setId(103)
+      .setKey("PORTABILITY")
+      .setName("Portability")
+      .setParent(new DefaultCharacteristic()
+        .setId(102)
+        .setKey("COMPILER_RELATED_PORTABILITY")
+        .setName("Compiler")));
+
+    durations = new Durations(new Settings().setProperty("sonar.technicalDebt.hoursInDay", 8), null);
+    ruleDao = new RuleDao(getMyBatis());
+
+    provider = new RulesProvider();
+  }
+
+  @Test
+  public void build_rules() throws Exception {
+    setupData("shared");
+
+    Rules rules = provider.provide(ruleDao, debtModel, durations);
+
+    assertThat(rules.findAll()).hasSize(1);
+    assertThat(rules.findByRepository("checkstyle")).hasSize(1);
+    assertThat(rules.findByRepository("unknown")).isEmpty();
+
+    Rule rule = rules.find(RuleKey.of("checkstyle", "AvoidNull"));
+    assertThat(rule).isNotNull();
+    assertThat(rule.key()).isEqualTo(RuleKey.of("checkstyle", "AvoidNull"));
+    assertThat(rule.name()).isEqualTo("Avoid Null");
+    assertThat(rule.description()).isEqualTo("Should avoid NULL");
+    assertThat(rule.severity()).isEqualTo(Severity.MINOR);
+    assertThat(rule.metadata()).isNull();
+    assertThat(rule.params()).hasSize(1);
+
+    RuleParam param = rule.param("myParameter");
+    assertThat(param).isNotNull();
+    assertThat(param.description()).isEqualTo("My Parameter");
+  }
+
+  @Test
+  public void build_rules_with_default_debt_definitions() throws Exception {
+    setupData("build_rules_with_default_debt_definitions");
+
+    Rules rules = provider.provide(ruleDao, debtModel, durations);
+
+    Rule rule = rules.find(RuleKey.of("checkstyle", "AvoidNull"));
+    assertThat(rule.characteristic()).isEqualTo("EFFICIENCY");
+    assertThat(rule.function()).isEqualTo(RemediationFunction.LINEAR_OFFSET);
+    assertThat(rule.factor()).isEqualTo(Duration.decode("5d", 8));
+    assertThat(rule.offset()).isEqualTo(Duration.decode("10h", 8));
+  }
+
+  @Test
+  public void build_rules_with_user_debt_definitions() throws Exception {
+    setupData("build_rules_with_user_debt_definitions");
+
+    Rules rules = provider.provide(ruleDao, debtModel, durations);
+
+    Rule rule = rules.find(RuleKey.of("checkstyle", "AvoidNull"));
+    assertThat(rule.characteristic()).isEqualTo("PORTABILITY");
+    assertThat(rule.function()).isEqualTo(RemediationFunction.LINEAR);
+    assertThat(rule.factor()).isEqualTo(Duration.decode("2h", 8));
+    assertThat(rule.offset()).isNull();
+  }
+
+  @Test
+  public void build_rules_with_default_and_user_debt_definitions() throws Exception {
+    setupData("build_rules_with_default_and_user_debt_definitions");
+
+    Rules rules = provider.provide(ruleDao, debtModel, durations);
+
+    // As both default columns and user columns on debt are set, user debt columns should be used
+    Rule rule = rules.find(RuleKey.of("checkstyle", "AvoidNull"));
+    assertThat(rule.characteristic()).isEqualTo("PORTABILITY");
+    assertThat(rule.function()).isEqualTo(RemediationFunction.LINEAR);
+    assertThat(rule.factor()).isEqualTo(Duration.decode("2h", 8));
+    assertThat(rule.offset()).isNull();
+  }
+
+  @Test
+  public void fail_if_characteristic_not_found() throws Exception {
+    setupData("fail_if_characteristic_not_found");
+
+    try {
+      provider.provide(ruleDao, debtModel, durations);
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Characteristic id '999' on rule 'checkstyle:AvoidNull' has not been found");
+    }
+  }
+
+  @Test
+  public void fail_if_no_function() throws Exception {
+    setupData("fail_if_no_function");
+
+    try {
+      provider.provide(ruleDao, debtModel, durations);
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Remediation function should not be null on rule 'checkstyle:AvoidNull'");
+    }
+  }
+}
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_default_and_user_debt_definitions.xml b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_default_and_user_debt_definitions.xml
new file mode 100644 (file)
index 0000000..5090b80
--- /dev/null
@@ -0,0 +1,29 @@
+<!--
+  ~ 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.
+  -->
+
+<dataset>
+
+  <rules id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle" name="Avoid Null" description="Should avoid NULL" status="READY" priority="1"
+         characteristic_id="103" default_characteristic_id="101"
+         remediation_function="LINEAR" default_remediation_function="LINEAR_OFFSET"
+         remediation_factor="2h" default_remediation_factor="5d"
+         remediation_offset="[null]" default_remediation_offset="10h"/>
+
+</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_default_debt_definitions.xml b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_default_debt_definitions.xml
new file mode 100644 (file)
index 0000000..024cc36
--- /dev/null
@@ -0,0 +1,29 @@
+<!--
+  ~ 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.
+  -->
+
+<dataset>
+
+  <rules id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle" name="Avoid Null" description="Should avoid NULL" status="READY" priority="1"
+         characteristic_id="[null]" default_characteristic_id="101"
+         remediation_function="[null]" default_remediation_function="LINEAR_OFFSET"
+         remediation_factor="[null]" default_remediation_factor="5d"
+         remediation_offset="[null]" default_remediation_offset="10h"/>
+
+</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_user_debt_definitions.xml b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_user_debt_definitions.xml
new file mode 100644 (file)
index 0000000..97b8965
--- /dev/null
@@ -0,0 +1,29 @@
+<!--
+  ~ 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.
+  -->
+
+<dataset>
+
+  <rules id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle" name="Avoid Null" description="Should avoid NULL" status="READY" priority="1"
+         characteristic_id="103" default_characteristic_id="[null]"
+         remediation_function="LINEAR" default_remediation_function="[null]"
+         remediation_factor="2h" default_remediation_factor="[null]"
+         remediation_offset="[null]" default_remediation_offset="[null]"/>
+
+</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/fail_if_characteristic_not_found.xml b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/fail_if_characteristic_not_found.xml
new file mode 100644 (file)
index 0000000..005d684
--- /dev/null
@@ -0,0 +1,29 @@
+<!--
+  ~ 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.
+  -->
+
+<dataset>
+
+  <rules id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle" name="Avoid Null" description="Should avoid NULL" status="READY" priority="1"
+         characteristic_id="[null]" default_characteristic_id="999"
+         remediation_function="[null]" default_remediation_function="LINEAR_OFFSET"
+         remediation_factor="[null]" default_remediation_factor="5d"
+         remediation_offset="[null]" default_remediation_offset="10h"/>
+
+</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/fail_if_no_function.xml b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/fail_if_no_function.xml
new file mode 100644 (file)
index 0000000..7c06972
--- /dev/null
@@ -0,0 +1,29 @@
+<!--
+  ~ 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.
+  -->
+
+<dataset>
+
+  <rules id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle" name="Avoid Null" description="Should avoid NULL" status="READY" priority="1"
+         characteristic_id="[null]" default_characteristic_id="101"
+         remediation_function="[null]" default_remediation_function="[null]"
+         remediation_factor="[null]" default_remediation_factor="5d"
+         remediation_offset="[null]" default_remediation_offset="10h"/>
+
+</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/shared.xml b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/shared.xml
new file mode 100644 (file)
index 0000000..916cabb
--- /dev/null
@@ -0,0 +1,31 @@
+<!--
+  ~ 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.
+  -->
+
+<dataset>
+
+  <rules id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle" name="Avoid Null" description="Should avoid NULL" status="READY" priority="1"
+         characteristic_id="[null]" default_characteristic_id="101"
+         remediation_function="[null]" default_remediation_function="LINEAR_OFFSET"
+         remediation_factor="[null]" default_remediation_factor="5d"
+         remediation_offset="[null]" default_remediation_offset="10h"/>
+
+  <rules_parameters id="1" rule_id="1" name="myParameter" param_type="plop" default_value="plouf" description="My Parameter"/>
+
+</dataset>
index da73eaa709fe82721564efec102721c8c42eef1f..8207b8ce7bc5d4abf4cc8b737d0ff47d65711891 100644 (file)
@@ -25,6 +25,7 @@ import org.sonar.api.ServerComponent;
 import org.sonar.core.persistence.MyBatis;
 
 import javax.annotation.CheckForNull;
+
 import java.util.Collection;
 import java.util.List;
 
@@ -45,6 +46,15 @@ public class RuleDao implements BatchComponent, ServerComponent {
     }
   }
 
+  public List<RuleDto> selectEnablesAndNonManual() {
+    SqlSession session = mybatis.openSession();
+    try {
+      return getMapper(session).selectEnablesAndNonManual();
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
   public List<RuleDto> selectNonManual(SqlSession session) {
     return getMapper(session).selectNonManual();
   }
index c3b62c5af75a1447283b333844709bdaf7671464..fc7832933a703a7d17fdb2075d386dfe086fd7cb 100644 (file)
@@ -26,6 +26,8 @@ import java.util.List;
 public interface RuleMapper {
   List<RuleDto> selectAll();
 
+  List<RuleDto> selectEnablesAndNonManual();
+
   List<RuleDto> selectNonManual();
 
   RuleDto selectById(Integer id);
index 823064f62042a43ad861c48416876f4cad48e83a..9257520c8abfbe77367a11971d83a2d3bcf07549 100644 (file)
@@ -68,6 +68,10 @@ public class DefaultTechnicalDebtManager implements TechnicalDebtManager {
     return null;
   }
 
+  /**
+   * @deprecated since 4.3
+   */
+  @Deprecated
   @CheckForNull
   public Characteristic findRequirementByRuleId(int ruleId) {
     CharacteristicDto requirementDto = dao.selectByRuleId(ruleId);
@@ -81,6 +85,10 @@ public class DefaultTechnicalDebtManager implements TechnicalDebtManager {
     return null;
   }
 
+  /**
+   * @deprecated since 4.3
+   */
+  @Deprecated
   @CheckForNull
   public Characteristic findRequirementByRule(Rule rule) {
     CharacteristicDto requirementDto = dao.selectByRuleId(rule.getId());
index c4d558add8e717bf70b5a1185adaa13677ce32fe..8bbf979396c43ed552e62ef856538049f02e9b3a 100644 (file)
     select <include refid="selectColumns"/> from rules
   </select>
 
+  <select id="selectEnablesAndNonManual" resultType="Rule">
+    select <include refid="selectColumns"/> from rules
+    <where>
+      and status != 'REMOVED'
+      and plugin_name != 'manual'
+    </where>
+  </select>
+
   <select id="selectById" parameterType="Integer" resultType="Rule">
     select <include refid="selectColumns"/> from rules WHERE id=#{id}
   </select>
index f30352b988b19184253d43fe486494755d0650ea..720ef32601a82f9b93650621418a56eb2edcd8cc 100644 (file)
@@ -66,6 +66,30 @@ public class RuleDaoTest extends AbstractDaoTestCase {
     assertThat(ruleDto.getEffortToFixL10nKey()).isEqualTo("squid.S115.effortToFix");
   }
 
+  @Test
+  public void select_enables_and_non_manual() throws Exception {
+    setupData("select_enables_and_non_manual");
+    List<RuleDto> ruleDtos = dao.selectEnablesAndNonManual();
+
+    assertThat(ruleDtos.size()).isEqualTo(1);
+    RuleDto ruleDto = ruleDtos.get(0);
+    assertThat(ruleDto.getId()).isEqualTo(1);
+    assertThat(ruleDto.getName()).isEqualTo("Avoid Null");
+    assertThat(ruleDto.getDescription()).isEqualTo("Should avoid NULL");
+    assertThat(ruleDto.getStatus()).isEqualTo(Rule.STATUS_READY);
+    assertThat(ruleDto.getRepositoryKey()).isEqualTo("checkstyle");
+    assertThat(ruleDto.getNoteData()).isEqualTo("Rule note with accents \u00e9\u00e8\u00e0");
+    assertThat(ruleDto.getCharacteristicId()).isEqualTo(100);
+    assertThat(ruleDto.getDefaultCharacteristicId()).isEqualTo(101);
+    assertThat(ruleDto.getRemediationFunction()).isEqualTo("LINEAR");
+    assertThat(ruleDto.getDefaultRemediationFunction()).isEqualTo("LINEAR_OFFSET");
+    assertThat(ruleDto.getRemediationFactor()).isEqualTo("1h");
+    assertThat(ruleDto.getDefaultRemediationFactor()).isEqualTo("5d");
+    assertThat(ruleDto.getRemediationOffset()).isEqualTo("5min");
+    assertThat(ruleDto.getDefaultRemediationOffset()).isEqualTo("10h");
+    assertThat(ruleDto.getEffortToFixL10nKey()).isEqualTo("squid.S115.effortToFix");
+  }
+
   @Test
   public void select_by_id() throws Exception {
     setupData("selectById");
index ef857e167d1f15e2c80b74b576f66d7657ef8d7e..05e793599563eed44cd2ba369647ceaf7f5608d7 100644 (file)
@@ -22,10 +22,7 @@ package org.sonar.core.technicaldebt;
 
 import org.junit.Before;
 import org.junit.Test;
-import org.sonar.api.rule.RuleKey;
 import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic;
-import org.sonar.api.technicaldebt.batch.internal.DefaultRequirement;
-import org.sonar.api.utils.internal.WorkDuration;
 
 import static org.fest.assertions.Assertions.assertThat;
 
@@ -76,30 +73,4 @@ public class DefaultTechnicalDebtModelTest {
     assertThat(sqaleModel.characteristicByKey("UNKNOWN")).isNull();
   }
 
-  @Test
-  public void get_requirement_by_rule_key() throws Exception {
-    DefaultCharacteristic rootCharacteristic = new DefaultCharacteristic()
-      .setKey("MEMORY_EFFICIENCY")
-      .setName("Memory use");
-
-    DefaultCharacteristic characteristic = new DefaultCharacteristic()
-      .setKey("EFFICIENCY")
-      .setName("Efficiency")
-      .setParent(rootCharacteristic);
-
-    RuleKey ruleKey = RuleKey.of("checkstyle", "Regexp");
-    DefaultRequirement requirement = new DefaultRequirement()
-      .setCharacteristic(characteristic)
-      .setRuleKey(ruleKey)
-      .setFunction("linear")
-      .setFactorValue(2)
-      .setFactorUnit(WorkDuration.UNIT.HOURS)
-      .setOffsetValue(0)
-      .setOffsetUnit(WorkDuration.UNIT.HOURS);
-
-    sqaleModel.addRootCharacteristic(rootCharacteristic);
-
-    assertThat(sqaleModel.requirementsByRule(ruleKey)).isEqualTo(requirement);
-    assertThat(sqaleModel.requirementsByRule(RuleKey.of("not", "found"))).isNull();
-  }
 }
diff --git a/sonar-core/src/test/resources/org/sonar/core/rule/RuleDaoTest/select_enables_and_non_manual.xml b/sonar-core/src/test/resources/org/sonar/core/rule/RuleDaoTest/select_enables_and_non_manual.xml
new file mode 100644 (file)
index 0000000..bbc29ba
--- /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.
+  -->
+
+<dataset>
+
+  <rules id="1" plugin_rule_key="AvoidNull" plugin_name="checkstyle" name="Avoid Null" description="Should avoid NULL" status="READY"
+         note_data="Rule note with accents Ã©Ã¨Ã " note_user_login="polop.palap" note_created_at="2013-12-25"
+         characteristic_id="100" default_characteristic_id="101"
+         remediation_function="LINEAR" default_remediation_function="LINEAR_OFFSET"
+         remediation_factor="1h" default_remediation_factor="5d"
+         remediation_offset="5min" default_remediation_offset="10h"
+         effort_to_fix_l10n_key="squid.S115.effortToFix"/>
+
+  <rules id="2" plugin_rule_key="AvoidNull" plugin_name="squid" name="Avoid Null" description="Should avoid NULL" status="REMOVED"
+         note_data="[null]" note_user_login="[null]" note_created_at="[null]"
+         characteristic_id="[null]" default_characteristic_id="[null]"
+         remediation_function="[null]" default_remediation_function="[null]"
+         remediation_factor="[null]" default_remediation_factor="[null]"
+         remediation_offset="[null]" default_remediation_offset="[null]"
+         effort_to_fix_l10n_key="[null]"/>
+
+  <rules id="3" plugin_rule_key="AvoidNull" plugin_name="manual" name="Manual Rule" description="Should not appear" status="READY"
+         note_data="[null]" note_user_login="[null]" note_created_at="[null]"
+         characteristic_id="[null]" default_characteristic_id="[null]"
+         remediation_function="[null]" default_remediation_function="[null]"
+         remediation_factor="[null]" default_remediation_factor="[null]"
+         remediation_offset="[null]" default_remediation_offset="[null]"
+         effort_to_fix_l10n_key="[null]"/>
+
+</dataset>
index 1db2c7b50ed13aba0b2e59245653251454efd3ab..10f4966632614f0321d47bc40ca42273ac09746a 100644 (file)
 package org.sonar.api.batch.rule;
 
 import com.google.common.annotations.Beta;
+import org.sonar.api.rule.RemediationFunction;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.utils.Duration;
 
 import javax.annotation.CheckForNull;
+
 import java.util.Collection;
 
 /**
- * Not used
  * @since 4.2
  */
 @Beta
@@ -52,4 +54,36 @@ public interface Rule {
 
   RuleStatus status();
 
+  /**
+   * Characteristic key.
+   *
+   * @since 4.3
+   */
+  @CheckForNull
+  String characteristic();
+
+  /**
+   * Remediation function : one of LINEAR, LINEAR_OFFSET or CONSTANT_ISSUE.
+   *
+   * @since 4.3
+   */
+  @CheckForNull
+  RemediationFunction function();
+
+  /**
+   * Remediation factor duration (used for LINEAR function).
+   *
+   * @since 4.3
+   */
+  @CheckForNull
+  Duration factor();
+
+  /**
+   * Remediation offset duration (used for LINEAR_OFFSET or CONSTANT_ISSUE function).
+   *
+   * @since 4.3
+   */
+  @CheckForNull
+  Duration offset();
+
 }
index b322ebf8571b346c0ebf6aacc706b378cd56456a..80fd72fef55a167e7eeb282b11cf73534932bb41 100644 (file)
@@ -22,7 +22,6 @@ package org.sonar.api.batch.rule;
 import com.google.common.annotations.Beta;
 
 /**
- * Not used
  * @since 4.2
  */
 @Beta
index ae3b41f8333e48d808a248824e0b82563e39bd59..3b417972000c777352a45a1104ad02213c8e0cf2 100644 (file)
@@ -23,10 +23,10 @@ import com.google.common.annotations.Beta;
 import org.sonar.api.rule.RuleKey;
 
 import javax.annotation.CheckForNull;
+
 import java.util.Collection;
 
 /**
- * Not used yet
  * @since 4.2
  */
 @Beta
index 784425b5d2a2fea1a5645f9425e862c6bf28fba8..8e4f6e862873d978f745b1f4e7d54810e09d3cb8 100644 (file)
@@ -22,11 +22,14 @@ package org.sonar.api.batch.rule.internal;
 import com.google.common.collect.ImmutableMap;
 import org.sonar.api.batch.rule.Rule;
 import org.sonar.api.batch.rule.RuleParam;
+import org.sonar.api.rule.RemediationFunction;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.utils.Duration;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.concurrent.Immutable;
+
 import java.util.Collection;
 import java.util.Map;
 
@@ -35,8 +38,11 @@ public class DefaultRule implements Rule {
 
   private final RuleKey key;
   private final Integer id;
-  private final String name, severity, description, metadata;
+  private final String name, severity, description, metadata, characteristic;
   private final RuleStatus status;
+  RemediationFunction function;
+  Duration factor, offset;
+
   private final Map<String, RuleParam> params;
 
   DefaultRule(NewRule newRule) {
@@ -47,6 +53,10 @@ public class DefaultRule implements Rule {
     this.description = newRule.description;
     this.metadata = newRule.metadata;
     this.status = newRule.status;
+    this.characteristic = newRule.characteristic;
+    this.function = newRule.function;
+    this.factor = newRule.factor;
+    this.offset = newRule.offset;
 
     ImmutableMap.Builder<String, RuleParam> builder = ImmutableMap.builder();
     for (NewRuleParam newRuleParam : newRule.params.values()) {
@@ -90,6 +100,26 @@ public class DefaultRule implements Rule {
     return status;
   }
 
+  @Override
+  public String characteristic() {
+    return characteristic;
+  }
+
+  @Override
+  public RemediationFunction function() {
+    return function;
+  }
+
+  @Override
+  public Duration factor() {
+    return factor;
+  }
+
+  @Override
+  public Duration offset() {
+    return offset;
+  }
+
   @Override
   public RuleParam param(String paramKey) {
     return params.get(paramKey);
index b55e0104ea96844718185e742e00489c44a3b995..9a20e04fb7e6061b2cbb76e93a04f7631938ebd4 100644 (file)
@@ -27,6 +27,7 @@ import org.sonar.api.batch.rule.Rules;
 import org.sonar.api.rule.RuleKey;
 
 import javax.annotation.concurrent.Immutable;
+
 import java.util.Collection;
 import java.util.List;
 
@@ -45,7 +46,6 @@ class DefaultRules implements Rules {
     rulesByRepository = builder.build();
   }
 
-
   @Override
   public Rule find(RuleKey ruleKey) {
     List<Rule> rules = rulesByRepository.get(ruleKey.repository());
index 4e22c0da58d199c757f3c7e948de39bac1a801a4..8bb25749a7f3836f73c054a005aaf9bb779c6007 100644 (file)
@@ -21,11 +21,14 @@ package org.sonar.api.batch.rule.internal;
 
 import org.apache.commons.lang.ObjectUtils;
 import org.apache.commons.lang.StringUtils;
+import org.sonar.api.rule.RemediationFunction;
 import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.Severity;
 import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.utils.Duration;
 
 import javax.annotation.Nullable;
+
 import java.util.HashMap;
 import java.util.Map;
 
@@ -35,7 +38,9 @@ public class NewRule {
 
   final RuleKey key;
   Integer id;
-  String name, description, severity = DEFAULT_SEVERITY, metadata;
+  String name, description, severity = DEFAULT_SEVERITY, metadata, characteristic;
+  RemediationFunction function;
+  Duration factor, offset;
   RuleStatus status = RuleStatus.defaultStatus();
   Map<String, NewRuleParam> params = new HashMap<String, NewRuleParam>();
 
@@ -73,6 +78,26 @@ public class NewRule {
     return this;
   }
 
+  public NewRule setCharacteristic(@Nullable String c) {
+    this.characteristic = c;
+    return this;
+  }
+
+  public NewRule setFunction(@Nullable RemediationFunction f) {
+    this.function = f;
+    return this;
+  }
+
+  public NewRule setFactor(@Nullable Duration f) {
+    this.factor = f;
+    return this;
+  }
+
+  public NewRule setOffset(@Nullable Duration o) {
+    this.offset = o;
+    return this;
+  }
+
   public NewRuleParam addParam(String paramKey) {
     if (params.containsKey(paramKey)) {
       throw new IllegalStateException(String.format("Parameter '%s' already exists on rule '%s'", paramKey, key));
index 851700262e7a2206ab1f65f1b3f2a74616432cb1..0161bc738befc05c40f8fb2f2faa26b278a361de 100644 (file)
@@ -21,6 +21,8 @@
 package org.sonar.api.rule;
 
 /**
+ * List of availables remediation function
+ *
  * @since 4.3
  */
 public enum RemediationFunction {
index 2f670b2bb767d3fd587454e0c9e6f1eea0abf4bc..b669135b68463cb69557397844eb0bbc93f13c42 100644 (file)
@@ -43,8 +43,6 @@ public interface Characteristic {
 
   List<? extends Characteristic> children();
 
-  List<? extends Requirement> requirements();
-
   boolean isRoot();
 
   Date createdAt();
index 31f2ff781ed19a0dd53953238a292de443a19998..d6eb82895b5e4836bd9e964185fc1f08c4f4db22 100644 (file)
@@ -28,7 +28,9 @@ import java.util.Date;
 
 /**
  * @since 4.1
+ * @deprecated since 4.3
  */
+@Deprecated
 public interface Requirement {
 
   Integer id();
index a4d1bf425093f0859396f5c93e065c30b8f9f12b..ad99777dc58c0f0b120ad347d4bccbb475f56d68 100644 (file)
@@ -37,12 +37,24 @@ public interface TechnicalDebtModel {
   @CheckForNull
   Characteristic characteristicByKey(String key);
 
+  /**
+   * @deprecated since 4.3
+   */
   @CheckForNull
+  @Deprecated
   Requirement requirementsByRule(RuleKey ruleKey);
 
+  /**
+   * @deprecated since 4.3
+   */
   @CheckForNull
+  @Deprecated
   Requirement requirementsById(Integer id);
 
+  /**
+   * @deprecated since 4.3
+   */
+  @Deprecated
   List<? extends Requirement> requirements();
 
 }
index b46196312a27d80cec887c462356e802994b25fd..cf604216971feff341e396bd353d5e3d34e9c2dc 100644 (file)
@@ -32,6 +32,10 @@ import javax.annotation.CheckForNull;
 
 import java.util.Date;
 
+/**
+ * @deprecated since 4.3
+ */
+@Deprecated
 public class DefaultRequirement implements Requirement {
 
   public static final String FUNCTION_LINEAR = "linear";
index 1befa3cb282c2652857a577c4ca225952e554e2d..a1347ecacd5ea7f2cac189a86f171c8fc60d9a80 100644 (file)
@@ -89,6 +89,10 @@ public interface Characteristic {
 
   boolean isRoot();
 
+  /**
+   * @deprecated since 4.3
+   */
+  @Deprecated
   boolean isRequirement();
 
 }
index e013aee2be0d2d300f6ff5b261fdf4211f588665..f97294c381ea0dae8a22055c0955d680024c3150 100644 (file)
@@ -33,6 +33,10 @@ public interface TechnicalDebtManager extends ServerComponent {
 
   List<Characteristic> findRootCharacteristics();
 
+  /**
+   * @deprecated since 4.3
+   */
+  @Deprecated
   Characteristic findRequirementByRule(Rule rule);
 
   Characteristic findCharacteristicById(Integer id);
index 12e8d64d562968ad7cb6a986e8ab67e82ea8ccfb..06918b3b3a6f12667ace8572552e643e1d04dec2 100644 (file)
@@ -151,11 +151,19 @@ public class DefaultCharacteristic implements Characteristic {
     return this;
   }
 
+  /**
+   * @deprecated since 4.3
+   */
+  @Deprecated
   @CheckForNull
   public Integer factorValue() {
     return factorValue;
   }
 
+  /**
+   * @deprecated since 4.3
+   */
+  @Deprecated
   public DefaultCharacteristic setFactorValue(@Nullable Integer factorValue) {
     this.factorValue = factorValue;
     return this;
@@ -166,6 +174,10 @@ public class DefaultCharacteristic implements Characteristic {
     return factorUnit;
   }
 
+  /**
+   * @deprecated since 4.3
+   */
+  @Deprecated
   public DefaultCharacteristic setFactorUnit(@Nullable WorkDuration.UNIT factorUnit) {
     this.factorUnit = factorUnit;
     return this;
@@ -194,26 +206,46 @@ public class DefaultCharacteristic implements Characteristic {
     return this;
   }
 
+  /**
+   * @deprecated since 4.3
+   */
+  @Deprecated
   @CheckForNull
   public Integer offsetValue() {
     return offsetValue;
   }
 
+  /**
+   * @deprecated since 4.3
+   */
+  @Deprecated
   public DefaultCharacteristic setOffsetValue(@Nullable Integer offsetValue) {
     this.offsetValue = offsetValue;
     return this;
   }
 
+  /**
+   * @deprecated since 4.3
+   */
+  @Deprecated
   @CheckForNull
   public WorkDuration.UNIT offsetUnit() {
     return offsetUnit;
   }
 
+  /**
+   * @deprecated since 4.3
+   */
+  @Deprecated
   public DefaultCharacteristic setOffsetUnit(@Nullable WorkDuration.UNIT offsetUnit) {
     this.offsetUnit = offsetUnit;
     return this;
   }
 
+  /**
+   * @deprecated since 4.3
+   */
+  @Deprecated
   public static WorkDuration.UNIT toUnit(@Nullable String requirementUnit) {
     if (requirementUnit != null) {
       if (WorkUnit.DAYS.equals(requirementUnit)) {
@@ -243,6 +275,10 @@ public class DefaultCharacteristic implements Characteristic {
     return parentId == null;
   }
 
+  /**
+   * @deprecated since 4.3
+   */
+  @Deprecated
   public boolean isRequirement() {
     return ruleKey != null;
   }
index ac61864d84bbdfdb2c9bc7dfe8e85ee8ff0f060c..504e6be26493e01629f481495738c3fc2fbe1e4a 100644 (file)
@@ -97,10 +97,22 @@ public class Duration implements Serializable {
     return durationInMinutes;
   }
 
-  public boolean isGreaterThan(Duration other){
+  public boolean isGreaterThan(Duration other) {
     return toMinutes() > other.toMinutes();
   }
 
+  public Duration add(Duration with) {
+    return Duration.create(durationInMinutes + with.durationInMinutes);
+  }
+
+  public Duration subtract(Duration with) {
+    return Duration.create(durationInMinutes - with.durationInMinutes);
+  }
+
+  public Duration multiply(int factor) {
+    return Duration.create(durationInMinutes * factor);
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
index cf7eb04d72be9969aefccb042bc8bd821de2e95e..ed99a98301e853cfc6bbe73f07ad5080c5f152d9 100644 (file)
@@ -75,6 +75,21 @@ public class DurationTest {
     assertThat(Duration.decode("1d", 10).isGreaterThan(Duration.decode("1d", 8))).isTrue();
   }
 
+  @Test
+  public void add() throws Exception {
+    assertThat(Duration.decode("1h", HOURS_IN_DAY).add(Duration.decode("1min", HOURS_IN_DAY))).isEqualTo(Duration.decode("1h1min", HOURS_IN_DAY));
+  }
+
+  @Test
+  public void subtract() throws Exception {
+    assertThat(Duration.decode("1h", HOURS_IN_DAY).subtract(Duration.decode("1min", HOURS_IN_DAY))).isEqualTo(Duration.decode("59min", HOURS_IN_DAY));
+  }
+
+  @Test
+  public void multiply() throws Exception {
+    assertThat(Duration.decode("1h", HOURS_IN_DAY).multiply(2)).isEqualTo(Duration.decode("2h", HOURS_IN_DAY));
+  }
+
   @Test
   public void test_equals_and_hashcode() throws Exception {
     Duration duration = Duration.create(ONE_DAY_IN_MINUTES + ONE_HOUR_IN_MINUTES + ONE_MINUTE);