diff options
43 files changed, 1113 insertions, 683 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java index 8d4c893f436..359f348cc7c 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecorator.java @@ -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; + } + } diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java index 248e2921192..cc98836f049 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/TechnicalDebtDecoratorTest.java @@ -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()); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java b/sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java index 88ce12563d1..6e6f000d511 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java +++ b/sonar-batch/src/main/java/org/sonar/batch/DefaultTimeMachine.java @@ -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 index 1cbf38d6835..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/debt/DebtModelLoader.java +++ /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; - } - -} diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/DebtModelProvider.java b/sonar-batch/src/main/java/org/sonar/batch/debt/DebtModelProvider.java index fafeade5489..237bfa3d93b 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/debt/DebtModelProvider.java +++ b/sonar-batch/src/main/java/org/sonar/batch/debt/DebtModelProvider.java @@ -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 index 54798761191..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/debt/RuleDebtCalculator.java +++ /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); - } - -} diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java b/sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java index d19ad5de7ad..b204cd6605c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java @@ -19,18 +19,19 @@ */ 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 index 00000000000..c36b70c0e3b --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/rule/RulesProvider.java @@ -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); + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index 4d3f6876bdd..5e0446c8ae7 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -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/DebtModelProviderTest.java index 2de08c8f77a..50e08fc793c 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/debt/DebtModelLoaderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/debt/DebtModelProviderTest.java @@ -27,34 +27,27 @@ 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 { +public class DebtModelProviderTest { @Mock CharacteristicDao dao; - @Mock - RuleFinder ruleFinder; + DebtModelProvider provider; - DebtModelLoader loader; @Before public void before() { - loader = new DebtModelLoader(dao, ruleFinder); + provider = new DebtModelProvider(); } @Test @@ -83,17 +76,15 @@ public class DebtModelLoaderTest { 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)); + when(dao.selectCharacteristics()).thenReturn(newArrayList(rootCharacteristicDto, characteristicDto, requirementDto)); - DefaultTechnicalDebtModel result = (DefaultTechnicalDebtModel) loader.load(); + 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.requirements()).isEmpty(); assertThat(rootCharacteristic.children()).hasSize(1); assertThat(rootCharacteristic.children().get(0).key()).isEqualTo("EFFICIENCY"); @@ -102,16 +93,6 @@ public class DebtModelLoaderTest { 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/RuleDebtCalculatorTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/RuleDebtCalculatorTest.java deleted file mode 100644 index c53cdcdc2d5..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/debt/RuleDebtCalculatorTest.java +++ /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 index 05747f800e5..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/debt/TechnicalDebtModelProviderTest.java +++ /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); - } -} diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java index 1150211081e..b8c4d1efbf1 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java @@ -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 index 00000000000..dc5d9b9e6e9 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/rule/RulesProviderTest.java @@ -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 index 00000000000..5090b80b139 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_default_and_user_debt_definitions.xml @@ -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 index 00000000000..024cc36f848 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_default_debt_definitions.xml @@ -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 index 00000000000..97b8965b1a8 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/build_rules_with_user_debt_definitions.xml @@ -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 index 00000000000..005d68415d8 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/fail_if_characteristic_not_found.xml @@ -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 index 00000000000..7c069728ff9 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/fail_if_no_function.xml @@ -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 index 00000000000..916cabb500c --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/rule/RulesProviderTest/shared.xml @@ -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> diff --git a/sonar-core/src/main/java/org/sonar/core/rule/RuleDao.java b/sonar-core/src/main/java/org/sonar/core/rule/RuleDao.java index da73eaa709f..8207b8ce7bc 100644 --- a/sonar-core/src/main/java/org/sonar/core/rule/RuleDao.java +++ b/sonar-core/src/main/java/org/sonar/core/rule/RuleDao.java @@ -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(); } diff --git a/sonar-core/src/main/java/org/sonar/core/rule/RuleMapper.java b/sonar-core/src/main/java/org/sonar/core/rule/RuleMapper.java index c3b62c5af75..fc7832933a7 100644 --- a/sonar-core/src/main/java/org/sonar/core/rule/RuleMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/rule/RuleMapper.java @@ -26,6 +26,8 @@ import java.util.List; public interface RuleMapper { List<RuleDto> selectAll(); + List<RuleDto> selectEnablesAndNonManual(); + List<RuleDto> selectNonManual(); RuleDto selectById(Integer id); diff --git a/sonar-core/src/main/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtManager.java b/sonar-core/src/main/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtManager.java index 823064f6204..9257520c8ab 100644 --- a/sonar-core/src/main/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtManager.java +++ b/sonar-core/src/main/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtManager.java @@ -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()); diff --git a/sonar-core/src/main/resources/org/sonar/core/rule/RuleMapper.xml b/sonar-core/src/main/resources/org/sonar/core/rule/RuleMapper.xml index c4d558add8e..8bbf979396c 100644 --- a/sonar-core/src/main/resources/org/sonar/core/rule/RuleMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/rule/RuleMapper.xml @@ -36,6 +36,14 @@ 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> diff --git a/sonar-core/src/test/java/org/sonar/core/rule/RuleDaoTest.java b/sonar-core/src/test/java/org/sonar/core/rule/RuleDaoTest.java index f30352b988b..720ef32601a 100644 --- a/sonar-core/src/test/java/org/sonar/core/rule/RuleDaoTest.java +++ b/sonar-core/src/test/java/org/sonar/core/rule/RuleDaoTest.java @@ -67,6 +67,30 @@ public class RuleDaoTest extends AbstractDaoTestCase { } @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"); RuleDto ruleDto = dao.selectById(2); diff --git a/sonar-core/src/test/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtModelTest.java b/sonar-core/src/test/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtModelTest.java index ef857e167d1..05e79359956 100644 --- a/sonar-core/src/test/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtModelTest.java +++ b/sonar-core/src/test/java/org/sonar/core/technicaldebt/DefaultTechnicalDebtModelTest.java @@ -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 index 00000000000..bbc29ba75e2 --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/rule/RuleDaoTest/select_enables_and_non_manual.xml @@ -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> diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rule.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rule.java index 1db2c7b50ed..10f49666326 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rule.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rule.java @@ -20,14 +20,16 @@ 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(); + } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/RuleParam.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/RuleParam.java index b322ebf8571..80fd72fef55 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/RuleParam.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/RuleParam.java @@ -22,7 +22,6 @@ package org.sonar.api.batch.rule; import com.google.common.annotations.Beta; /** - * Not used * @since 4.2 */ @Beta diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rules.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rules.java index ae3b41f8333..3b417972000 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rules.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rules.java @@ -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 diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRule.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRule.java index 784425b5d2a..8e4f6e86287 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRule.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRule.java @@ -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()) { @@ -91,6 +101,26 @@ public class DefaultRule implements Rule { } @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); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRules.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRules.java index b55e0104ea9..9a20e04fb7e 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRules.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRules.java @@ -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()); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/NewRule.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/NewRule.java index 4e22c0da58d..8bb25749a7f 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/NewRule.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/NewRule.java @@ -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)); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/rule/RemediationFunction.java b/sonar-plugin-api/src/main/java/org/sonar/api/rule/RemediationFunction.java index 851700262e7..0161bc738be 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/rule/RemediationFunction.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/rule/RemediationFunction.java @@ -21,6 +21,8 @@ package org.sonar.api.rule; /** + * List of availables remediation function + * * @since 4.3 */ public enum RemediationFunction { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/Characteristic.java b/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/Characteristic.java index 2f670b2bb76..b669135b684 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/Characteristic.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/Characteristic.java @@ -43,8 +43,6 @@ public interface Characteristic { List<? extends Characteristic> children(); - List<? extends Requirement> requirements(); - boolean isRoot(); Date createdAt(); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/Requirement.java b/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/Requirement.java index 31f2ff781ed..d6eb82895b5 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/Requirement.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/Requirement.java @@ -28,7 +28,9 @@ import java.util.Date; /** * @since 4.1 + * @deprecated since 4.3 */ +@Deprecated public interface Requirement { Integer id(); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/TechnicalDebtModel.java b/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/TechnicalDebtModel.java index a4d1bf42509..ad99777dc58 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/TechnicalDebtModel.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/TechnicalDebtModel.java @@ -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(); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/internal/DefaultRequirement.java b/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/internal/DefaultRequirement.java index b46196312a2..cf604216971 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/internal/DefaultRequirement.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/batch/internal/DefaultRequirement.java @@ -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"; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/server/Characteristic.java b/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/server/Characteristic.java index 1befa3cb282..a1347ecacd5 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/server/Characteristic.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/server/Characteristic.java @@ -89,6 +89,10 @@ public interface Characteristic { boolean isRoot(); + /** + * @deprecated since 4.3 + */ + @Deprecated boolean isRequirement(); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/server/TechnicalDebtManager.java b/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/server/TechnicalDebtManager.java index e013aee2be0..f97294c381e 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/server/TechnicalDebtManager.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/server/TechnicalDebtManager.java @@ -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); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/server/internal/DefaultCharacteristic.java b/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/server/internal/DefaultCharacteristic.java index 12e8d64d562..06918b3b3a6 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/server/internal/DefaultCharacteristic.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/technicaldebt/server/internal/DefaultCharacteristic.java @@ -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; } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/Duration.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/Duration.java index ac61864d84b..504e6be2649 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/Duration.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/Duration.java @@ -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) { diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/utils/DurationTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/utils/DurationTest.java index cf7eb04d72b..ed99a98301e 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/utils/DurationTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/utils/DurationTest.java @@ -76,6 +76,21 @@ public class DurationTest { } @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); Duration durationWithSameValue = Duration.create(ONE_DAY_IN_MINUTES + ONE_HOUR_IN_MINUTES + ONE_MINUTE); |