From 66041bc94f8c7c8e0a2ab0aafe3895e3b1b98df1 Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Wed, 18 Sep 2013 16:11:11 +0200 Subject: [PATCH] SONAR-4689 Batch API to get issues of the current module --- .../org/sonar/batch/index/DefaultIndex.java | 10 ++-- .../sonar/batch/issue/DefaultIssuable.java | 8 +-- ...anIssues.java => DefaultModuleIssues.java} | 34 ++++++++++- .../sonar/batch/issue/IssuableFactory.java | 8 +-- .../sonar/batch/scan/ModuleScanContainer.java | 6 +- .../sonar/batch/index/DefaultIndexTest.java | 4 +- .../batch/issue/DefaultIssuableTest.java | 6 +- ...Test.java => DefaultModuleIssuesTest.java} | 58 ++++++++++++++----- .../batch/issue/IssuableFactoryTest.java | 10 ++-- .../org/sonar/api/issue/ModuleIssues.java | 41 +++++++++++++ 10 files changed, 143 insertions(+), 42 deletions(-) rename sonar-batch/src/main/java/org/sonar/batch/issue/{ScanIssues.java => DefaultModuleIssues.java} (72%) rename sonar-batch/src/test/java/org/sonar/batch/issue/{ScanIssuesTest.java => DefaultModuleIssuesTest.java} (72%) create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/issue/ModuleIssues.java diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java index caa4a98b0b2..ca8cf60059c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java @@ -49,7 +49,7 @@ import org.sonar.batch.DefaultResourceCreationLock; import org.sonar.batch.ProjectTree; import org.sonar.batch.ResourceFilters; import org.sonar.batch.issue.DeprecatedViolations; -import org.sonar.batch.issue.ScanIssues; +import org.sonar.batch.issue.DefaultModuleIssues; import org.sonar.core.component.ComponentKeys; import org.sonar.core.component.ScanGraph; @@ -82,7 +82,7 @@ public class DefaultIndex extends SonarIndex { private Map> incomingDependenciesByResource = Maps.newHashMap(); private ProjectTree projectTree; private final DeprecatedViolations deprecatedViolations; - private ScanIssues scanIssues; + private DefaultModuleIssues moduleIssues; public DefaultIndex(PersistenceManager persistence, DefaultResourceCreationLock lock, ProjectTree projectTree, MetricFinder metricFinder, ScanGraph graph, DeprecatedViolations deprecatedViolations) { @@ -124,12 +124,12 @@ public class DefaultIndex extends SonarIndex { return currentProject; } - public void setCurrentProject(Project project, ResourceFilters resourceFilters, ScanIssues scanIssues) { + public void setCurrentProject(Project project, ResourceFilters resourceFilters, DefaultModuleIssues moduleIssues) { this.currentProject = project; // the following components depend on the current module, so they need to be reloaded. this.resourceFilters = resourceFilters; - this.scanIssues = scanIssues; + this.moduleIssues = moduleIssues; } /** @@ -378,7 +378,7 @@ public class DefaultIndex extends SonarIndex { violation.setSeverity(null); violation.setResource(bucket.getResource()); - scanIssues.initAndAddViolation(violation); + moduleIssues.initAndAddViolation(violation); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssuable.java b/sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssuable.java index 0be18b5dff9..5b7c4c8092c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssuable.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssuable.java @@ -33,13 +33,13 @@ import java.util.List; */ public class DefaultIssuable implements Issuable { - private final ScanIssues scanIssues; + private final DefaultModuleIssues moduleIssues; private final IssueCache cache; private final Component component; - DefaultIssuable(Component component, ScanIssues scanIssues, IssueCache cache) { + DefaultIssuable(Component component, DefaultModuleIssues moduleIssues, IssueCache cache) { this.component = component; - this.scanIssues = scanIssues; + this.moduleIssues = moduleIssues; this.cache = cache; } @@ -50,7 +50,7 @@ public class DefaultIssuable implements Issuable { @Override public boolean addIssue(Issue issue) { - return scanIssues.initAndAddIssue((DefaultIssue) issue); + return moduleIssues.initAndAddIssue((DefaultIssue) issue); } @SuppressWarnings("unchecked") diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssues.java b/sonar-batch/src/main/java/org/sonar/batch/issue/DefaultModuleIssues.java similarity index 72% rename from sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssues.java rename to sonar-batch/src/main/java/org/sonar/batch/issue/DefaultModuleIssues.java index a3b53260897..cdd9f22223a 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssues.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/DefaultModuleIssues.java @@ -19,7 +19,10 @@ */ package org.sonar.batch.issue; -import org.sonar.api.BatchComponent; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.ModuleIssues; import org.sonar.api.issue.internal.DefaultIssue; import org.sonar.api.profiles.RulesProfile; import org.sonar.api.resources.Project; @@ -33,14 +36,14 @@ import javax.annotation.Nullable; /** * Initialize the issues raised during scan. */ -public class ScanIssues implements BatchComponent { +public class DefaultModuleIssues implements ModuleIssues { private final RulesProfile qProfile; private final IssueCache cache; private final Project project; private final IssueFilters filters; - public ScanIssues(RulesProfile qProfile, IssueCache cache, Project project, IssueFilters filters) { + public DefaultModuleIssues(RulesProfile qProfile, IssueCache cache, Project project, IssueFilters filters) { this.qProfile = qProfile; this.cache = cache; this.project = project; @@ -88,4 +91,29 @@ public class ScanIssues implements BatchComponent { return false; } + @Override + public Iterable issues() { + return (Iterable) Iterables.filter(cache.all(), new ModulePredicate(false)); + } + + @Override + public Iterable resolvedIssues() { + return (Iterable) Iterables.filter(cache.all(), new ModulePredicate(true)); + } + + private class ModulePredicate implements Predicate { + private final boolean resolved; + + private ModulePredicate(boolean resolved) { + this.resolved = resolved; + } + + @Override + public boolean apply(@Nullable DefaultIssue issue) { + if (issue != null && (issue.componentKey().equals(project.getEffectiveKey()) || issue.componentKey().startsWith(project.getEffectiveKey() + ":"))) { + return resolved ? issue.resolution() != null : issue.resolution()==null; + } + return false; + } + } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/IssuableFactory.java b/sonar-batch/src/main/java/org/sonar/batch/issue/IssuableFactory.java index 3e13158a761..cd34582c24c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/IssuableFactory.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/IssuableFactory.java @@ -33,12 +33,12 @@ import javax.annotation.CheckForNull; */ public class IssuableFactory extends PerspectiveBuilder { - private final ScanIssues scanIssues; + private final DefaultModuleIssues moduleIssues; private final IssueCache cache; - public IssuableFactory(ScanIssues scanIssues, IssueCache cache) { + public IssuableFactory(DefaultModuleIssues moduleIssues, IssueCache cache) { super(Issuable.class); - this.scanIssues = scanIssues; + this.moduleIssues = moduleIssues; this.cache = cache; } @@ -49,6 +49,6 @@ public class IssuableFactory extends PerspectiveBuilder { if (component instanceof ResourceComponent) { supported = Scopes.isHigherThanOrEquals(((ResourceComponent) component).scope(), Scopes.FILE); } - return supported ? new DefaultIssuable(component, scanIssues, cache) : null; + return supported ? new DefaultIssuable(component, moduleIssues, cache) : null; } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java index 0075238fd4e..543f01e2940 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java @@ -42,7 +42,7 @@ import org.sonar.batch.index.DefaultIndex; import org.sonar.batch.index.ResourcePersister; import org.sonar.batch.issue.IssuableFactory; import org.sonar.batch.issue.IssueFilters; -import org.sonar.batch.issue.ScanIssues; +import org.sonar.batch.issue.DefaultModuleIssues; import org.sonar.batch.phases.PhaseExecutor; import org.sonar.batch.phases.PhasesTimeProfiler; import org.sonar.batch.scan.filesystem.*; @@ -113,7 +113,7 @@ public class ModuleScanContainer extends ComponentContainer { new ProfileProvider(), // issues - ScanIssues.class, + DefaultModuleIssues.class, IssuableFactory.class, ScanPerspectives.class @@ -140,7 +140,7 @@ public class ModuleScanContainer extends ComponentContainer { DefaultIndex index = getComponentByType(DefaultIndex.class); index.setCurrentProject(module, getComponentByType(ResourceFilters.class), - getComponentByType(ScanIssues.class)); + getComponentByType(DefaultModuleIssues.class)); getComponentByType(PhaseExecutor.class).execute(module); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java index e3f2e17300a..2431cefbbb5 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java @@ -39,7 +39,7 @@ import org.sonar.batch.DefaultResourceCreationLock; import org.sonar.batch.ProjectTree; import org.sonar.batch.ResourceFilters; import org.sonar.batch.issue.DeprecatedViolations; -import org.sonar.batch.issue.ScanIssues; +import org.sonar.batch.issue.DefaultModuleIssues; import org.sonar.core.component.ScanGraph; import static com.google.common.collect.Lists.newArrayList; @@ -77,7 +77,7 @@ public class DefaultIndexTest { rule = Rule.create("repoKey", "ruleKey", "Rule"); rule.setId(1); rulesProfile.activateRule(rule, null); - index.setCurrentProject(project, new ResourceFilters(new ResourceFilter[]{filter}), mock(ScanIssues.class)); + index.setCurrentProject(project, new ResourceFilters(new ResourceFilter[]{filter}), mock(DefaultModuleIssues.class)); index.doStart(project); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/DefaultIssuableTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/DefaultIssuableTest.java index a4ffb41416e..f66cc03aa16 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/DefaultIssuableTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/DefaultIssuableTest.java @@ -33,7 +33,7 @@ import static org.mockito.Mockito.when; public class DefaultIssuableTest { - ScanIssues scanIssues = mock(ScanIssues.class); + DefaultModuleIssues moduleIssues = mock(DefaultModuleIssues.class); IssueCache cache = mock(IssueCache.class); Component component = mock(Component.class); @@ -44,7 +44,7 @@ public class DefaultIssuableTest { DefaultIssue unresolved = new DefaultIssue(); when(cache.byComponent("struts:org.apache.Action")).thenReturn(Arrays.asList(resolved, unresolved)); - DefaultIssuable perspective = new DefaultIssuable(component, scanIssues, cache); + DefaultIssuable perspective = new DefaultIssuable(component, moduleIssues, cache); List issues = perspective.issues(); assertThat(issues).containsOnly(unresolved); @@ -57,7 +57,7 @@ public class DefaultIssuableTest { DefaultIssue unresolved = new DefaultIssue(); when(cache.byComponent("struts:org.apache.Action")).thenReturn(Arrays.asList(resolved, unresolved)); - DefaultIssuable perspective = new DefaultIssuable(component, scanIssues, cache); + DefaultIssuable perspective = new DefaultIssuable(component, moduleIssues, cache); List issues = perspective.resolvedIssues(); assertThat(issues).containsOnly(resolved); diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssuesTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/DefaultModuleIssuesTest.java similarity index 72% rename from sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssuesTest.java rename to sonar-batch/src/test/java/org/sonar/batch/issue/DefaultModuleIssuesTest.java index f67ad671308..27f14f4bcd0 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssuesTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/DefaultModuleIssuesTest.java @@ -19,10 +19,12 @@ */ package org.sonar.batch.issue; +import com.google.common.collect.Lists; import org.apache.commons.lang.time.DateUtils; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.sonar.api.issue.Issue; import org.sonar.api.issue.internal.DefaultIssue; import org.sonar.api.profiles.RulesProfile; import org.sonar.api.resources.JavaFile; @@ -35,30 +37,36 @@ import org.sonar.api.rules.Rule; import org.sonar.api.rules.RulePriority; import org.sonar.api.rules.Violation; +import java.util.Arrays; import java.util.Calendar; import java.util.Date; +import java.util.List; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Mockito.*; -public class ScanIssuesTest { +public class DefaultModuleIssuesTest { + + static final RuleKey SQUID_RULE_KEY = RuleKey.of("squid", "AvoidCycle"); IssueCache cache = mock(IssueCache.class); RulesProfile qProfile = mock(RulesProfile.class); Project project = mock(Project.class); IssueFilters filters = mock(IssueFilters.class); - ScanIssues scanIssues = new ScanIssues(qProfile, cache, project, filters); + DefaultModuleIssues moduleIssues = new DefaultModuleIssues(qProfile, cache, project, filters); @Before public void setUp() { when(project.getAnalysisDate()).thenReturn(new Date()); + when(project.getEffectiveKey()).thenReturn("org.apache:struts-core"); } + @Test public void should_ignore_null_active_rule() throws Exception { when(qProfile.getActiveRule(anyString(), anyString())).thenReturn(null); - DefaultIssue issue = new DefaultIssue().setRuleKey(RuleKey.of("squid", "AvoidCycle")); - boolean added = scanIssues.initAndAddIssue(issue); + DefaultIssue issue = new DefaultIssue().setRuleKey(SQUID_RULE_KEY); + boolean added = moduleIssues.initAndAddIssue(issue); assertThat(added).isFalse(); verifyZeroInteractions(cache); @@ -70,8 +78,8 @@ public class ScanIssuesTest { when(activeRule.getRule()).thenReturn(null); when(qProfile.getActiveRule(anyString(), anyString())).thenReturn(activeRule); - DefaultIssue issue = new DefaultIssue().setRuleKey(RuleKey.of("squid", "AvoidCycle")); - boolean added = scanIssues.initAndAddIssue(issue); + DefaultIssue issue = new DefaultIssue().setRuleKey(SQUID_RULE_KEY); + boolean added = moduleIssues.initAndAddIssue(issue); assertThat(added).isFalse(); verifyZeroInteractions(cache); @@ -90,11 +98,11 @@ public class ScanIssuesTest { DefaultIssue issue = new DefaultIssue() .setKey("ABCDE") - .setRuleKey(RuleKey.of("squid", "AvoidCycle")) + .setRuleKey(SQUID_RULE_KEY) .setSeverity(Severity.CRITICAL); when(filters.accept(issue, null)).thenReturn(true); - boolean added = scanIssues.initAndAddIssue(issue); + boolean added = moduleIssues.initAndAddIssue(issue); assertThat(added).isTrue(); ArgumentCaptor argument = ArgumentCaptor.forClass(DefaultIssue.class); @@ -114,9 +122,9 @@ public class ScanIssuesTest { Date analysisDate = new Date(); when(project.getAnalysisDate()).thenReturn(analysisDate); - DefaultIssue issue = new DefaultIssue().setRuleKey(RuleKey.of("squid", "AvoidCycle")).setSeverity(null); + DefaultIssue issue = new DefaultIssue().setRuleKey(SQUID_RULE_KEY).setSeverity(null); when(filters.accept(issue, null)).thenReturn(true); - scanIssues.initAndAddIssue(issue); + moduleIssues.initAndAddIssue(issue); ArgumentCaptor argument = ArgumentCaptor.forClass(DefaultIssue.class); verify(cache).put(argument.capture()); @@ -139,7 +147,7 @@ public class ScanIssuesTest { when(qProfile.getActiveRule("squid", "AvoidCycle")).thenReturn(activeRule); when(filters.accept(any(DefaultIssue.class), eq(violation))).thenReturn(true); - boolean added = scanIssues.initAndAddViolation(violation); + boolean added = moduleIssues.initAndAddViolation(violation); assertThat(added).isTrue(); ArgumentCaptor argument = ArgumentCaptor.forClass(DefaultIssue.class); @@ -163,14 +171,38 @@ public class ScanIssuesTest { DefaultIssue issue = new DefaultIssue() .setKey("ABCDE") - .setRuleKey(RuleKey.of("squid", "AvoidCycle")) + .setRuleKey(SQUID_RULE_KEY) .setSeverity(Severity.CRITICAL); when(filters.accept(issue, null)).thenReturn(false); - boolean added = scanIssues.initAndAddIssue(issue); + boolean added = moduleIssues.initAndAddIssue(issue); assertThat(added).isFalse(); verifyZeroInteractions(cache); } + + @Test + public void should_get_module_issues() throws Exception { + DefaultIssue issueOnModule = new DefaultIssue().setKey("1").setRuleKey(SQUID_RULE_KEY).setComponentKey("org.apache:struts-core"); + DefaultIssue issueInModule = new DefaultIssue().setKey("2").setRuleKey(SQUID_RULE_KEY).setComponentKey("org.apache:struts-core:Action"); + DefaultIssue resolvedIssueInModule = new DefaultIssue().setKey("3").setRuleKey(SQUID_RULE_KEY).setComponentKey("org.apache:struts-core:Action").setResolution(Issue.RESOLUTION_FIXED); + + when(cache.all()).thenReturn(Arrays.asList( + // issue on root module + new DefaultIssue().setKey("4").setRuleKey(SQUID_RULE_KEY).setSeverity(Severity.CRITICAL).setComponentKey("org.apache:struts"), + + // issue in root module + new DefaultIssue().setKey("5").setRuleKey(SQUID_RULE_KEY).setSeverity(Severity.CRITICAL).setComponentKey("org.apache:struts:FileInRoot"), + + issueOnModule, issueInModule, resolvedIssueInModule + )); + + // unresolved issues + List issues = Lists.newArrayList(moduleIssues.issues()); + assertThat(issues).containsOnly(issueInModule, issueOnModule); + + List resolvedIssues = Lists.newArrayList(moduleIssues.resolvedIssues()); + assertThat(resolvedIssues).containsOnly(resolvedIssueInModule); + } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/IssuableFactoryTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/IssuableFactoryTest.java index 9bc450ac5fd..65e4b5d706c 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/IssuableFactoryTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/IssuableFactoryTest.java @@ -34,12 +34,12 @@ import static org.mockito.Mockito.mock; public class IssuableFactoryTest { - ScanIssues scanIssues = mock(ScanIssues.class); + DefaultModuleIssues moduleIssues = mock(DefaultModuleIssues.class); IssueCache cache = mock(IssueCache.class, Mockito.RETURNS_MOCKS); @Test public void file_should_be_issuable() throws Exception { - IssuableFactory factory = new IssuableFactory(scanIssues, cache); + IssuableFactory factory = new IssuableFactory(moduleIssues, cache); Component component = new ResourceComponent(new File("foo/bar.c").setEffectiveKey("foo/bar.c")); Issuable issuable = factory.loadPerspective(Issuable.class, component); @@ -50,7 +50,7 @@ public class IssuableFactoryTest { @Test public void project_should_be_issuable() throws Exception { - IssuableFactory factory = new IssuableFactory(scanIssues, cache); + IssuableFactory factory = new IssuableFactory(moduleIssues, cache); Component component = new ResourceComponent(new Project("Foo").setEffectiveKey("foo")); Issuable issuable = factory.loadPerspective(Issuable.class, component); @@ -61,7 +61,7 @@ public class IssuableFactoryTest { @Test public void java_file_should_be_issuable() throws Exception { - IssuableFactory factory = new IssuableFactory(scanIssues, cache); + IssuableFactory factory = new IssuableFactory(moduleIssues, cache); Component component = new ResourceComponent(new JavaFile("org.apache.Action").setEffectiveKey("struts:org.apache.Action")); Issuable issuable = factory.loadPerspective(Issuable.class, component); @@ -72,7 +72,7 @@ public class IssuableFactoryTest { @Test public void java_class_should_not_be_issuable() throws Exception { - IssuableFactory factory = new IssuableFactory(scanIssues, cache); + IssuableFactory factory = new IssuableFactory(moduleIssues, cache); Component component = new ResourceComponent(JavaClass.create("org.apache.Action").setEffectiveKey("struts:org.apache.Action")); Issuable issuable = factory.loadPerspective(Issuable.class, component); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/ModuleIssues.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/ModuleIssues.java new file mode 100644 index 00000000000..2f1a974f51c --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/ModuleIssues.java @@ -0,0 +1,41 @@ +/* + * 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.api.issue; + +import org.sonar.api.BatchComponent; + +/** + * Used by batch components to get the issues of the current module. It does not allow + * to get issues of all project modules. + * + * @since 4.0 + */ +public interface ModuleIssues extends BatchComponent { + + /** + * All the unresolved issues of the current module, including the issues reported by end-users. + */ + Iterable issues(); + + /** + * All the issues of this module that have been marked as resolved during this scan + */ + Iterable resolvedIssues(); +} -- 2.39.5