From b8eecc041ad4aa23a3bf5cf7c8847573b125d187 Mon Sep 17 00:00:00 2001 From: simonbrandhof Date: Fri, 28 Jan 2011 13:17:57 +0100 Subject: [PATCH] SONAR-2149 Resource filters are ignored in complexity distributions of Java projects This issue implies SONAR-2153 : API: A decorator should override formulas --- .../org/sonar/plugins/squid/SquidPlugin.java | 5 +- .../plugins/squid/bridges/BridgeFactory.java | 6 +- .../plugins/squid/bridges/ResourceIndex.java | 2 + .../ClassComplexityDistributionBuilder.java | 73 ++++++++++++ ...FunctionComplexityDistributionBuilder.java | 76 +++++++++++++ .../sonar/plugins/squid/SquidPluginTest.java | 2 +- ...ClassComplexityDistributionBridgeTest.java | 41 ------- ...ctionComplexityDistributionBridgeTest.java | 43 ------- ...lassComplexityDistributionBuilderTest.java | 57 ++++++++++ ...tionComplexityDistributionBuilderTest.java | 55 +++++++++ .../org/sonar/batch/DecoratorsSelector.java | 23 ++-- .../org/sonar}/batch/FormulaDecorator.java | 28 +++-- .../sonar/batch/DecoratorsSelectorTest.java | 36 +++--- .../sonar}/batch/FormulaDecoratorTest.java | 16 +-- .../java/org/sonar/java/api/JavaClass.java | 4 +- .../java/org/sonar/java/api/JavaMethod.java | 41 +++---- .../org/sonar/java/api/JavaClassTest.java | 14 +-- .../org/sonar/java/api/JavaMethodTest.java | 2 +- .../org/sonar/api/measures/CoreMetrics.java | 14 ++- .../measures/SumChildDistributionFormula.java | 23 +++- .../org/sonar/api/resources/BlockUnit.java | 104 +++++++++++++++++ .../SumChildDistributionFormulaTest.java | 2 + .../java/itests/ExcludedResourceFilter.java | 13 +++ .../src/main/java/itests/ITestsPlugin.java | 1 + .../maven-projects/java-complexity/pom.xml | 8 ++ .../main/java/foo/ContainsInnerClasses.java | 37 ++++++ .../src/main/java/foo/ExcludedByFilter.java | 19 ++++ .../src/main/java/foo/Helloworld.java | 42 +++++++ .../tests/integration/JavaComplexityIT.java | 107 ++++++++++++++++++ .../sonar/tests/integration/Struts139IT.java | 18 ++- 30 files changed, 737 insertions(+), 175 deletions(-) create mode 100644 plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/decorators/ClassComplexityDistributionBuilder.java create mode 100644 plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/decorators/FunctionComplexityDistributionBuilder.java delete mode 100644 plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/bridges/ClassComplexityDistributionBridgeTest.java delete mode 100644 plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/bridges/FunctionComplexityDistributionBridgeTest.java create mode 100644 plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/decorators/ClassComplexityDistributionBuilderTest.java create mode 100644 plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/decorators/FunctionComplexityDistributionBuilderTest.java rename {sonar-plugin-api/src/main/java/org/sonar/api => sonar-batch/src/main/java/org/sonar}/batch/FormulaDecorator.java (82%) rename {sonar-plugin-api/src/test/java/org/sonar/api => sonar-batch/src/test/java/org/sonar}/batch/FormulaDecoratorTest.java (95%) create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/resources/BlockUnit.java create mode 100644 tests/integration/sonar-it-reference-plugin/src/main/java/itests/ExcludedResourceFilter.java create mode 100644 tests/integration/tests/maven-projects/java-complexity/pom.xml create mode 100644 tests/integration/tests/maven-projects/java-complexity/src/main/java/foo/ContainsInnerClasses.java create mode 100644 tests/integration/tests/maven-projects/java-complexity/src/main/java/foo/ExcludedByFilter.java create mode 100644 tests/integration/tests/maven-projects/java-complexity/src/main/java/foo/Helloworld.java create mode 100644 tests/integration/tests/src/test/java/org/sonar/tests/integration/JavaComplexityIT.java diff --git a/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/SquidPlugin.java b/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/SquidPlugin.java index 856c3438fea..ea18236faef 100644 --- a/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/SquidPlugin.java +++ b/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/SquidPlugin.java @@ -23,6 +23,8 @@ import org.sonar.api.CoreProperties; import org.sonar.api.Plugin; import org.sonar.api.Properties; import org.sonar.api.Property; +import org.sonar.plugins.squid.decorators.ClassComplexityDistributionBuilder; +import org.sonar.plugins.squid.decorators.FunctionComplexityDistributionBuilder; import java.util.Arrays; import java.util.List; @@ -58,7 +60,8 @@ public class SquidPlugin implements Plugin { } public List getExtensions() { - return Arrays.asList(SquidSearchProxy.class, SquidSensor.class, SquidRuleRepository.class, JavaSourceImporter.class); + return Arrays.asList(SquidSearchProxy.class, SquidSensor.class, SquidRuleRepository.class, JavaSourceImporter.class, + ClassComplexityDistributionBuilder.class, FunctionComplexityDistributionBuilder.class); } @Override diff --git a/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/bridges/BridgeFactory.java b/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/bridges/BridgeFactory.java index 05b8726e1b1..b621e87b230 100644 --- a/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/bridges/BridgeFactory.java +++ b/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/bridges/BridgeFactory.java @@ -36,17 +36,17 @@ public final class BridgeFactory { private static List create(NoSonarFilter noSonarFilter) { return Arrays.asList(new CopyBasicMeasuresBridge(), new PackagesBridge(), new PublicUndocumentedApiBridge(), - new ClassComplexityDistributionBridge(), new FunctionComplexityDistributionBridge(), new NoSonarFilterLoader(noSonarFilter), + new NoSonarFilterLoader(noSonarFilter), new ChidamberKemererBridge(), new RobertCMartinBridge(), new ChidamberKemererDistributionBridge(), new DesignBridge(), new Lcom4BlocksBridge(), new ChecksBridge()); } public static List create(boolean bytecodeScanned, SensorContext context, CheckFactory checkFactory, - ResourceIndex resourceIndex, Squid squid, NoSonarFilter noSonarFilter) { + ResourceIndex resourceIndex, Squid squid, NoSonarFilter noSonarFilter) { List result = new ArrayList(); for (Bridge bridge : create(noSonarFilter)) { bridge.setCheckFactory(checkFactory); - if ( !bridge.needsBytecode() || bytecodeScanned) { + if (!bridge.needsBytecode() || bytecodeScanned) { bridge.setContext(context); bridge.setSquid(squid); bridge.setResourceIndex(resourceIndex); diff --git a/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/bridges/ResourceIndex.java b/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/bridges/ResourceIndex.java index f0720cdb7f8..105a33e6b2d 100644 --- a/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/bridges/ResourceIndex.java +++ b/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/bridges/ResourceIndex.java @@ -31,6 +31,7 @@ import org.sonar.java.api.JavaMethod; import org.sonar.squid.Squid; import org.sonar.squid.api.*; import org.sonar.squid.indexer.QueryByType; +import org.sonar.squid.measures.Metric; import java.util.Collection; import java.util.HashMap; @@ -93,6 +94,7 @@ public final class ResourceIndex extends HashMap { .setSignature(squidMethod.getName()) .setFromLine(squidMethod.getStartAtLine()) .setToLine(squidMethod.getEndAtLine()) + .setAccessor(squidMethod.getInt(Metric.ACCESSORS)>0) .create(); context.index(sonarMethod, sonarClass); diff --git a/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/decorators/ClassComplexityDistributionBuilder.java b/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/decorators/ClassComplexityDistributionBuilder.java new file mode 100644 index 00000000000..f0a0636f19c --- /dev/null +++ b/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/decorators/ClassComplexityDistributionBuilder.java @@ -0,0 +1,73 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.squid.decorators; + +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.DependedUpon; +import org.sonar.api.batch.DependsUpon; +import org.sonar.api.measures.*; +import org.sonar.api.resources.Java; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.api.resources.Scopes; + +/** + * @since 2.6 + */ +public final class ClassComplexityDistributionBuilder implements Decorator { + + public static final Number[] LIMITS = {0, 5, 10, 20, 30, 60, 90}; + + @DependsUpon + public Metric dependOnComplexity() { + return CoreMetrics.COMPLEXITY; + } + + @DependedUpon + public Metric generatesFunctionComplexityDistribution() { + return CoreMetrics.CLASS_COMPLEXITY_DISTRIBUTION; + } + + public void decorate(Resource resource, DecoratorContext context) { + if (shouldExecuteOn(resource, context)) { + RangeDistributionBuilder builder = new RangeDistributionBuilder(CoreMetrics.CLASS_COMPLEXITY_DISTRIBUTION, LIMITS); + for (DecoratorContext childContext : context.getChildren()) { + if (Scopes.isType(childContext.getResource())) { + Measure complexity = childContext.getMeasure(CoreMetrics.COMPLEXITY); + if (complexity != null) { + builder.add(complexity.getValue()); + } + } + } + Measure measure = builder.build(true); + measure.setPersistenceMode(PersistenceMode.MEMORY); + context.saveMeasure(measure); + } + } + + boolean shouldExecuteOn(Resource resource, DecoratorContext context) { + return Scopes.isFile(resource) && context.getMeasure(CoreMetrics.COMPLEXITY) != null; + } + + public boolean shouldExecuteOnProject(Project project) { + return Java.KEY.equals(project.getLanguageKey()); + } +} diff --git a/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/decorators/FunctionComplexityDistributionBuilder.java b/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/decorators/FunctionComplexityDistributionBuilder.java new file mode 100644 index 00000000000..75c5c28fd8e --- /dev/null +++ b/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/plugins/squid/decorators/FunctionComplexityDistributionBuilder.java @@ -0,0 +1,76 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.squid.decorators; + +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.DependedUpon; +import org.sonar.api.batch.DependsUpon; +import org.sonar.api.measures.*; +import org.sonar.api.resources.Java; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.api.resources.Scopes; +import org.sonar.java.api.JavaMethod; + +/** + * @since 2.6 + */ +public final class FunctionComplexityDistributionBuilder implements Decorator { + + public static final Number[] LIMITS = {1, 2, 4, 6, 8, 10, 12}; + + @DependsUpon + public Metric dependOnComplexity() { + return CoreMetrics.COMPLEXITY; + } + + @DependedUpon + public Metric generatesFunctionComplexityDistribution() { + return CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION; + } + + public void decorate(Resource resource, DecoratorContext context) { + if (shouldExecuteOn(resource, context)) { + RangeDistributionBuilder builder = new RangeDistributionBuilder(CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION, LIMITS); + for (DecoratorContext childContext : context.getChildren()) { + if (childContext.getResource() instanceof JavaMethod) { + JavaMethod javaMethod = (JavaMethod)childContext.getResource(); + Measure complexity = childContext.getMeasure(CoreMetrics.COMPLEXITY); + if (!javaMethod.isAccessor() && complexity != null) { + builder.add(complexity.getValue()); + } + } + } + Measure measure = builder.build(true); + measure.setPersistenceMode(PersistenceMode.MEMORY); + context.saveMeasure(measure); + } + } + + boolean shouldExecuteOn(Resource resource, DecoratorContext context) { + return Scopes.isType(resource) && context.getMeasure(CoreMetrics.COMPLEXITY) != null; + } + + public boolean shouldExecuteOnProject(Project project) { + return Java.KEY.equals(project.getLanguageKey()); + } +} + diff --git a/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/SquidPluginTest.java b/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/SquidPluginTest.java index 12a6cbed182..0387b501095 100644 --- a/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/SquidPluginTest.java +++ b/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/SquidPluginTest.java @@ -31,6 +31,6 @@ public class SquidPluginTest { @Test public void coverageForFun() { assertThat(new SquidPlugin().getKey(), not(nullValue())); - assertThat(new SquidPlugin().getExtensions().size(), is(4)); + assertThat(new SquidPlugin().getExtensions().size(), is(6)); } } diff --git a/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/bridges/ClassComplexityDistributionBridgeTest.java b/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/bridges/ClassComplexityDistributionBridgeTest.java deleted file mode 100644 index 9e132df3471..00000000000 --- a/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/bridges/ClassComplexityDistributionBridgeTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Sonar, open source software quality management tool. - * Copyright (C) 2009 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * Sonar 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. - * - * Sonar 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 Sonar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 - */ - -package org.sonar.plugins.squid.bridges; - -import org.junit.Test; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.never; -import static org.mockito.Matchers.*; -import org.sonar.api.resources.JavaFile; -import org.sonar.api.resources.JavaPackage; -import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.measures.Measure; -import org.sonar.api.test.IsMeasure; - -public class ClassComplexityDistributionBridgeTest extends BridgeTestCase { - - @Test - public void classComplexityDistribution() { - verify(context).saveMeasure(eq(new JavaPackage("org.apache.struts.config")), argThat(new IsMeasure(CoreMetrics.CLASS_COMPLEXITY_DISTRIBUTION, "0=10;5=3;10=2;20=1;30=4;60=4;90=1"))); - verify(context, never()).saveMeasure(eq(new JavaFile("org.apache.struts.config.ConfigRuleSet")), eq(new Measure(CoreMetrics.CLASS_COMPLEXITY_DISTRIBUTION, "equals() on measure only uses the metric"))); - verify(context, never()).saveMeasure(eq(project), eq(new Measure(CoreMetrics.CLASS_COMPLEXITY_DISTRIBUTION, "equals() on measure only uses the metric"))); - } -} diff --git a/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/bridges/FunctionComplexityDistributionBridgeTest.java b/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/bridges/FunctionComplexityDistributionBridgeTest.java deleted file mode 100644 index d5203779f6a..00000000000 --- a/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/bridges/FunctionComplexityDistributionBridgeTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Sonar, open source software quality management tool. - * Copyright (C) 2009 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * Sonar 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. - * - * Sonar 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 Sonar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 - */ - -package org.sonar.plugins.squid.bridges; - -import org.junit.Test; -import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.measures.Measure; -import org.sonar.api.resources.JavaFile; -import org.sonar.api.resources.JavaPackage; -import org.sonar.api.test.IsMeasure; - -import static org.mockito.Matchers.argThat; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -public class FunctionComplexityDistributionBridgeTest extends BridgeTestCase { - - @Test - public void functionComplexityDistribution() { - verify(context).saveMeasure(eq(new JavaPackage("org.apache.struts.config")), argThat(new IsMeasure(CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION, "1=186;2=88;4=11;6=12;8=7;10=2;12=8"))); - verify(context, never()).saveMeasure(eq(new JavaFile("org.apache.struts.config.ConfigRuleSet")), eq(new Measure(CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION, "equals() on measure only uses the metric"))); - verify(context, never()).saveMeasure(eq(project), eq(new Measure(CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION, "equals() on measure only uses the metric"))); - } -} diff --git a/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/decorators/ClassComplexityDistributionBuilderTest.java b/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/decorators/ClassComplexityDistributionBuilderTest.java new file mode 100644 index 00000000000..477643076d1 --- /dev/null +++ b/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/decorators/ClassComplexityDistributionBuilderTest.java @@ -0,0 +1,57 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.squid.decorators; + +import org.junit.Test; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.resources.Java; +import org.sonar.api.resources.JavaFile; +import org.sonar.api.resources.JavaPackage; +import org.sonar.api.resources.Project; +import org.sonar.java.api.JavaClass; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ClassComplexityDistributionBuilderTest { + + @Test + public void shouldExecuteOnJavaProjectsOnly() throws Exception { + ClassComplexityDistributionBuilder builder = new ClassComplexityDistributionBuilder(); + assertThat(builder.shouldExecuteOnProject(new Project("java").setLanguageKey(Java.KEY)), is(true)); + assertThat(builder.shouldExecuteOnProject(new Project("php").setLanguageKey("php")), is(false)); + } + + @Test + public void shouldExecuteOnFilesOnly() throws Exception { + ClassComplexityDistributionBuilder builder = new ClassComplexityDistributionBuilder(); + DecoratorContext context = mock(DecoratorContext.class); + when(context.getMeasure(CoreMetrics.COMPLEXITY)).thenReturn(new Measure(CoreMetrics.COMPLEXITY, 20.0)); + + assertThat(builder.shouldExecuteOn(new JavaPackage("org.foo"), context), is(false)); + assertThat(builder.shouldExecuteOn(new JavaFile("org.foo.Bar"), context), is(true)); + assertThat(builder.shouldExecuteOn(JavaClass.create("org.foo.Bar"), context), is(false)); + } + +} diff --git a/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/decorators/FunctionComplexityDistributionBuilderTest.java b/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/decorators/FunctionComplexityDistributionBuilderTest.java new file mode 100644 index 00000000000..aa0efe31cac --- /dev/null +++ b/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/plugins/squid/decorators/FunctionComplexityDistributionBuilderTest.java @@ -0,0 +1,55 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.squid.decorators; + +import org.junit.Test; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.resources.Java; +import org.sonar.api.resources.JavaFile; +import org.sonar.api.resources.JavaPackage; +import org.sonar.api.resources.Project; +import org.sonar.java.api.JavaClass; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class FunctionComplexityDistributionBuilderTest { + @Test + public void shouldExecuteOnJavaProjectsOnly() throws Exception { + FunctionComplexityDistributionBuilder builder = new FunctionComplexityDistributionBuilder(); + assertThat(builder.shouldExecuteOnProject(new Project("java").setLanguageKey(Java.KEY)), is(true)); + assertThat(builder.shouldExecuteOnProject(new Project("php").setLanguageKey("php")), is(false)); + } + + @Test + public void shouldExecuteOnClassesOnly() throws Exception { + FunctionComplexityDistributionBuilder builder = new FunctionComplexityDistributionBuilder(); + DecoratorContext context = mock(DecoratorContext.class); + when(context.getMeasure(CoreMetrics.COMPLEXITY)).thenReturn(new Measure(CoreMetrics.COMPLEXITY, 20.0)); + + assertThat(builder.shouldExecuteOn(new JavaPackage("org.foo"), context), is(false)); + assertThat(builder.shouldExecuteOn(new JavaFile("org.foo.Bar"), context), is(false)); + assertThat(builder.shouldExecuteOn(JavaClass.create("org.foo.Bar"), context), is(true)); + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/DecoratorsSelector.java b/sonar-batch/src/main/java/org/sonar/batch/DecoratorsSelector.java index 8f8c000099e..5aa54d0cfc4 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/DecoratorsSelector.java +++ b/sonar-batch/src/main/java/org/sonar/batch/DecoratorsSelector.java @@ -19,15 +19,16 @@ */ package org.sonar.batch; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.SetMultimap; import org.sonar.api.batch.BatchExtensionDictionnary; import org.sonar.api.batch.Decorator; -import org.sonar.api.batch.FormulaDecorator; import org.sonar.api.measures.Metric; import org.sonar.api.resources.Project; import java.util.*; -public class DecoratorsSelector { +public final class DecoratorsSelector { private BatchExtensionDictionnary dictionnary; @@ -37,26 +38,26 @@ public class DecoratorsSelector { public Collection select(Project project) { List decorators = new ArrayList(dictionnary.select(Decorator.class, project, false)); - Set coveredMetrics = getMetricsCoveredByPlugins(decorators); + SetMultimap decoratorsByGeneratedMetric = getDecoratorsByMetric(decorators); for (Metric metric : dictionnary.select(Metric.class)) { - if (metric.getFormula() != null && !coveredMetrics.contains(metric)) { - decorators.add(new FormulaDecorator(metric)); + if (metric.getFormula() != null) { + decorators.add(new FormulaDecorator(metric, decoratorsByGeneratedMetric.get(metric))); } } return dictionnary.sort(decorators); } - private Set getMetricsCoveredByPlugins(Collection pluginDecorators) { - Set coveredMetrics = new HashSet(); - for (Decorator pluginDecorator : pluginDecorators) { - List dependents = dictionnary.getDependents(pluginDecorator); + private SetMultimap getDecoratorsByMetric(Collection pluginDecorators) { + SetMultimap decoratorsByGeneratedMetric = HashMultimap.create(); + for (Decorator decorator : pluginDecorators) { + List dependents = dictionnary.getDependents(decorator); for (Object dependent : dependents) { if (dependent instanceof Metric) { - coveredMetrics.add((Metric) dependent); + decoratorsByGeneratedMetric.put((Metric) dependent, decorator); } } } - return coveredMetrics; + return decoratorsByGeneratedMetric; } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/FormulaDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/FormulaDecorator.java similarity index 82% rename from sonar-plugin-api/src/main/java/org/sonar/api/batch/FormulaDecorator.java rename to sonar-batch/src/main/java/org/sonar/batch/FormulaDecorator.java index a082bffc578..0c80290fb53 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/FormulaDecorator.java +++ b/sonar-batch/src/main/java/org/sonar/batch/FormulaDecorator.java @@ -17,24 +17,29 @@ * License along with Sonar; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 */ -package org.sonar.api.batch; +package org.sonar.batch; +import org.sonar.api.batch.*; import org.sonar.api.measures.FormulaData; import org.sonar.api.measures.Measure; import org.sonar.api.measures.Metric; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.Set; /** * A pre-implementation of a decorator using a simple calculation formula * @since 1.11 */ -public class FormulaDecorator implements Decorator { +public final class FormulaDecorator implements Decorator { private Metric metric; private DefaultFormulaContext formulaContext; + private Set executeAfterDecorators; /** * Creates a FormulaDecorator @@ -43,12 +48,17 @@ public class FormulaDecorator implements Decorator { * * @throws IllegalArgumentException if no formula is associated to the metric */ - public FormulaDecorator(Metric metric) { + public FormulaDecorator(Metric metric, Set executeAfterDecorators) { if (metric.getFormula() == null) { throw new IllegalArgumentException("No formula defined on metric"); } this.metric = metric; this.formulaContext = new DefaultFormulaContext(metric); + this.executeAfterDecorators = executeAfterDecorators; + } + + public FormulaDecorator(Metric metric) { + this(metric, Collections.emptySet()); } /** @@ -74,6 +84,11 @@ public class FormulaDecorator implements Decorator { return metric.getFormula().dependsUponMetrics(); } + @DependsUpon + public Collection dependsUponDecorators() { + return executeAfterDecorators; + } + /** * {@inheritDoc} */ @@ -98,13 +113,8 @@ public class FormulaDecorator implements Decorator { if (o == null || getClass() != o.getClass()) { return false; } - FormulaDecorator that = (FormulaDecorator) o; - - if (metric != null ? !metric.equals(that.metric) : that.metric != null) { - return false; - } - return true; + return !(metric != null ? !metric.equals(that.metric) : that.metric != null); } @Override diff --git a/sonar-batch/src/test/java/org/sonar/batch/DecoratorsSelectorTest.java b/sonar-batch/src/test/java/org/sonar/batch/DecoratorsSelectorTest.java index 90edc924f10..5439d9b12c4 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/DecoratorsSelectorTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/DecoratorsSelectorTest.java @@ -19,13 +19,13 @@ */ package org.sonar.batch; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; +import org.apache.commons.collections.CollectionUtils; import org.junit.Test; -import static org.junit.internal.matchers.IsCollectionContaining.hasItem; -import static org.mockito.Mockito.mock; import org.picocontainer.containers.TransientPicoContainer; -import org.sonar.api.batch.*; +import org.sonar.api.batch.BatchExtensionDictionnary; +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.DependedUpon; import org.sonar.api.measures.*; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; @@ -34,16 +34,20 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.internal.matchers.IsCollectionContaining.hasItem; + public class DecoratorsSelectorTest { private Metric withFormula1 = new Metric("metric1").setFormula(new FakeFormula()); private Metric withFormula2 = new Metric("metric2").setFormula(new FakeFormula()); - private Metric withoutFormula = new Metric("metric3"); + private Metric withoutFormula3 = new Metric("metric3"); @Test public void selectAndSortFormulas() { Project project = new Project("key"); - BatchExtensionDictionnary dictionnary = newDictionnary(withFormula1, withoutFormula, withFormula2); + BatchExtensionDictionnary dictionnary = newDictionnary(withFormula1, withoutFormula3, withFormula2); Collection decorators = new DecoratorsSelector(dictionnary).select(project); assertThat(decorators.size(), is(2)); @@ -52,18 +56,22 @@ public class DecoratorsSelectorTest { } @Test - public void pluginDecoratorsCanOverrideFormulas() { + public void decoratorsShouldBeExecutedBeforeFormulas() { Project project = new Project("key"); - Decorator fakeDecorator = new FakeDecorator(); Decorator metric1Decorator = new Metric1Decorator(); - BatchExtensionDictionnary dictionnary = newDictionnary(fakeDecorator, metric1Decorator, withFormula1, withoutFormula, withFormula2); + BatchExtensionDictionnary dictionnary = newDictionnary(metric1Decorator, withFormula1); Collection decorators = new DecoratorsSelector(dictionnary).select(project); - assertThat(decorators.size(), is(3)); - assertThat(decorators, hasItem(fakeDecorator)); - assertThat(decorators, hasItem(metric1Decorator)); - assertThat(decorators, hasItem((Decorator) new FormulaDecorator(withFormula2))); + Decorator firstDecorator = (Decorator)CollectionUtils.get(decorators, 0); + Decorator secondDecorator = (Decorator)CollectionUtils.get(decorators, 1); + + assertThat(firstDecorator, is(Metric1Decorator.class)); + assertThat(secondDecorator, is(FormulaDecorator.class)); + + FormulaDecorator formulaDecorator = (FormulaDecorator) secondDecorator; + assertThat(formulaDecorator.dependsUponDecorators().size(), is(1)); + assertThat(CollectionUtils.get(formulaDecorator.dependsUponDecorators(), 0), is((Object)firstDecorator)); } private BatchExtensionDictionnary newDictionnary(Object... extensions) { diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/FormulaDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/FormulaDecoratorTest.java similarity index 95% rename from sonar-plugin-api/src/test/java/org/sonar/api/batch/FormulaDecoratorTest.java rename to sonar-batch/src/test/java/org/sonar/batch/FormulaDecoratorTest.java index 50865b26ae0..b9e25d6bc4d 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/batch/FormulaDecoratorTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/FormulaDecoratorTest.java @@ -17,14 +17,10 @@ * License along with Sonar; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 */ -package org.sonar.api.batch; +package org.sonar.batch; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; import org.junit.Test; -import static org.junit.internal.matchers.IsCollectionContaining.hasItem; -import static org.mockito.Matchers.argThat; -import static org.mockito.Mockito.*; +import org.sonar.api.batch.DecoratorContext; import org.sonar.api.measures.*; import org.sonar.api.test.IsMeasure; @@ -32,6 +28,12 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.junit.internal.matchers.IsCollectionContaining.hasItem; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.*; + public class FormulaDecoratorTest { @Test @@ -50,7 +52,7 @@ public class FormulaDecoratorTest { return null; } }; - Metric metric = new Metric().setFormula(formula); + Metric metric = new Metric("ncloc").setFormula(formula); List dependencies = new FormulaDecorator(metric).dependsUponMetrics(); assertThat(dependencies, hasItem(CoreMetrics.COMPLEXITY)); assertThat(dependencies, hasItem(CoreMetrics.COVERAGE)); diff --git a/sonar-java-api/src/main/java/org/sonar/java/api/JavaClass.java b/sonar-java-api/src/main/java/org/sonar/java/api/JavaClass.java index 563879e23bb..d482c3d8bb2 100644 --- a/sonar-java-api/src/main/java/org/sonar/java/api/JavaClass.java +++ b/sonar-java-api/src/main/java/org/sonar/java/api/JavaClass.java @@ -109,11 +109,11 @@ public final class JavaClass extends Resource { return getName(); } - public static JavaClass createRef(String name) { + public static JavaClass create(String name) { return new JavaClass(name); } - public static JavaClass createRef(String packageName, String className) { + public static JavaClass create(String packageName, String className) { if (StringUtils.isBlank(packageName)) { return new JavaClass(className); } diff --git a/sonar-java-api/src/main/java/org/sonar/java/api/JavaMethod.java b/sonar-java-api/src/main/java/org/sonar/java/api/JavaMethod.java index 743b3fa4069..ce7eb14fe8b 100644 --- a/sonar-java-api/src/main/java/org/sonar/java/api/JavaMethod.java +++ b/sonar-java-api/src/main/java/org/sonar/java/api/JavaMethod.java @@ -25,9 +25,8 @@ import org.sonar.api.resources.*; /** * @since 2.6 */ -public final class JavaMethod extends Resource { +public final class JavaMethod extends BlockUnit { - public static final String SCOPE = Scopes.BLOCK_UNIT; public static final String QUALIFIER = Qualifiers.METHOD; public static final int UNKNOWN_LINE = -1; @@ -37,17 +36,19 @@ public final class JavaMethod extends Resource { private String className; private int fromLine; private int toLine; + private boolean isAccessor = false; private JavaMethod(String className, String signature) { - setKey(toKey(className, signature)); + super(toKey(className, signature), QUALIFIER, Java.INSTANCE); this.className = className; this.signature = signature; } - private JavaMethod(String className, String signature, int fromLine, int toLine) { + private JavaMethod(String className, String signature, int fromLine, int toLine, boolean isAccessor) { this(className, signature); this.fromLine = fromLine; this.toLine = toLine; + this.isAccessor = isAccessor; } public int getFromLine() { @@ -66,6 +67,10 @@ public final class JavaMethod extends Resource { return className; } + public boolean isAccessor() { + return isAccessor; + } + @Override public String getName() { return signature; @@ -81,31 +86,11 @@ public final class JavaMethod extends Resource { return null; } - @Override - public Language getLanguage() { - return Java.INSTANCE; - } - - @Override - public String getScope() { - return SCOPE; - } - - @Override - public String getQualifier() { - return QUALIFIER; - } - @Override public Resource getParent() { return null; } - @Override - public boolean matchFilePattern(String antPattern) { - return false; - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -155,6 +140,7 @@ public final class JavaMethod extends Resource { private String signature; private int fromLine = UNKNOWN_LINE; private int toLine = UNKNOWN_LINE; + private boolean isAccessor = false; public Builder setKey(String key) { String[] parts = splitClassAndMethodFromKey(key); @@ -188,8 +174,13 @@ public final class JavaMethod extends Resource { return this; } + public Builder setAccessor(boolean accessor) { + isAccessor = accessor; + return this; + } + public JavaMethod create() { - return new JavaMethod(className, signature, fromLine, toLine); + return new JavaMethod(className, signature, fromLine, toLine, isAccessor); } } } diff --git a/sonar-java-api/src/test/java/org/sonar/java/api/JavaClassTest.java b/sonar-java-api/src/test/java/org/sonar/java/api/JavaClassTest.java index 869f60b3be5..92eba48740a 100644 --- a/sonar-java-api/src/test/java/org/sonar/java/api/JavaClassTest.java +++ b/sonar-java-api/src/test/java/org/sonar/java/api/JavaClassTest.java @@ -30,7 +30,7 @@ public class JavaClassTest { @Test public void shouldCreateReferenceFromName() { - JavaClass javaClass = JavaClass.createRef("org.foo.Bar"); + JavaClass javaClass = JavaClass.create("org.foo.Bar"); assertThat(javaClass.getClassName(), is("Bar")); assertThat(javaClass.getKey(), is("org.foo.Bar")); assertThat(javaClass.getLanguage(), is((Language)Java.INSTANCE)); @@ -40,7 +40,7 @@ public class JavaClassTest { @Test public void shouldCreateReferenceFromPackageAndClassname() { - JavaClass javaClass = JavaClass.createRef("org.foo", "Bar"); + JavaClass javaClass = JavaClass.create("org.foo", "Bar"); assertThat(javaClass.getClassName(), is("Bar")); assertThat(javaClass.getKey(), is("org.foo.Bar")); assertThat(javaClass.getLanguage(), is((Language)Java.INSTANCE)); @@ -50,25 +50,25 @@ public class JavaClassTest { @Test public void shouldGetPackageName() { - JavaClass javaClass = JavaClass.createRef("org.foo.Bar"); + JavaClass javaClass = JavaClass.create("org.foo.Bar"); assertThat(javaClass.getPackageName(), is("org.foo")); - javaClass = JavaClass.createRef("Bar"); + javaClass = JavaClass.create("Bar"); assertThat(javaClass.getPackageName(), is("")); } @Test public void shouldGetClassName() { - JavaClass javaClass = JavaClass.createRef("org.foo.Bar"); + JavaClass javaClass = JavaClass.create("org.foo.Bar"); assertThat(javaClass.getClassName(), is("Bar")); - javaClass = JavaClass.createRef("Bar"); + javaClass = JavaClass.create("Bar"); assertThat(javaClass.getClassName(), is("Bar")); } @Test public void shouldOverrideToString() { - JavaClass javaClass = JavaClass.createRef("org.foo.Bar"); + JavaClass javaClass = JavaClass.create("org.foo.Bar"); assertThat(javaClass.toString(), is("org.foo.Bar")); } diff --git a/sonar-java-api/src/test/java/org/sonar/java/api/JavaMethodTest.java b/sonar-java-api/src/test/java/org/sonar/java/api/JavaMethodTest.java index bc6976aca25..fad1e992dfe 100644 --- a/sonar-java-api/src/test/java/org/sonar/java/api/JavaMethodTest.java +++ b/sonar-java-api/src/test/java/org/sonar/java/api/JavaMethodTest.java @@ -40,7 +40,7 @@ public class JavaMethodTest { public void shouldCreateReferenceFromClassAndSignature() { String className = "org.foo.Bar"; String signature = "hello(LString;)V"; - JavaMethod method = JavaMethod.createRef(JavaClass.createRef(className), signature); + JavaMethod method = JavaMethod.createRef(JavaClass.create(className), signature); assertThat(method.getKey(), is(className + "#" + signature)); assertThat(method.getClassName(), is(className)); assertThat(method.getName(), is(signature)); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java index 35f270d3c4d..ab1c36d8c0a 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java @@ -20,6 +20,7 @@ package org.sonar.api.measures; import org.apache.commons.lang.StringUtils; +import org.sonar.api.resources.Scopes; import org.sonar.api.utils.SonarException; import java.lang.reflect.Field; @@ -133,22 +134,22 @@ public final class CoreMetrics { public static final String CLASS_COMPLEXITY_DISTRIBUTION_KEY = "class_complexity_distribution"; public static final Metric CLASS_COMPLEXITY_DISTRIBUTION = new Metric(CLASS_COMPLEXITY_DISTRIBUTION_KEY, "Classes distribution /complexity", "Classes distribution /complexity", Metric.ValueType.DISTRIB, Metric.DIRECTION_NONE, true, - DOMAIN_COMPLEXITY).setFormula(new SumChildDistributionFormula()); + DOMAIN_COMPLEXITY).setFormula(new SumChildDistributionFormula().setMinimumScopeToPersist(Scopes.DIRECTORY)); public static final String FUNCTION_COMPLEXITY_DISTRIBUTION_KEY = "function_complexity_distribution"; public static final Metric FUNCTION_COMPLEXITY_DISTRIBUTION = new Metric(FUNCTION_COMPLEXITY_DISTRIBUTION_KEY, "Functions distribution /complexity", "Functions distribution /complexity", Metric.ValueType.DISTRIB, Metric.DIRECTION_NONE, true, - DOMAIN_COMPLEXITY).setFormula(new SumChildDistributionFormula()); + DOMAIN_COMPLEXITY).setFormula(new SumChildDistributionFormula().setMinimumScopeToPersist(Scopes.DIRECTORY)); public static final String FILE_COMPLEXITY_DISTRIBUTION_KEY = "file_complexity_distribution"; public static final Metric FILE_COMPLEXITY_DISTRIBUTION = new Metric(FILE_COMPLEXITY_DISTRIBUTION_KEY, "Files distribution /complexity", "Files distribution /complexity", Metric.ValueType.DISTRIB, Metric.DIRECTION_NONE, true, DOMAIN_COMPLEXITY) - .setFormula(new SumChildDistributionFormula()); + .setFormula(new SumChildDistributionFormula().setMinimumScopeToPersist(Scopes.DIRECTORY)); public static final String PARAGRAPH_COMPLEXITY_DISTRIBUTION_KEY = "paragraph_complexity_distribution"; public static final Metric PARAGRAPH_COMPLEXITY_DISTRIBUTION = new Metric(PARAGRAPH_COMPLEXITY_DISTRIBUTION_KEY, "Paragraph distribution /complexity", "Paragraph distribution /complexity", Metric.ValueType.DISTRIB, Metric.DIRECTION_NONE, true, - DOMAIN_COMPLEXITY).setFormula(new SumChildDistributionFormula()); + DOMAIN_COMPLEXITY).setFormula(new SumChildDistributionFormula().setMinimumScopeToPersist(Scopes.DIRECTORY)); public static final String COMMENT_LINES_KEY = "comment_lines"; public static final Metric COMMENT_LINES = new Metric(COMMENT_LINES_KEY, "Comment lines", "Number of comment lines", @@ -423,7 +424,8 @@ public final class CoreMetrics { public static final String RFC_DISTRIBUTION_KEY = "rfc_distribution"; public static final Metric RFC_DISTRIBUTION = new Metric(RFC_DISTRIBUTION_KEY, "Class distribution /RFC", "Class distribution /RFC", - Metric.ValueType.DISTRIB, Metric.DIRECTION_NONE, true, DOMAIN_DESIGN).setFormula(new SumChildDistributionFormula()); + Metric.ValueType.DISTRIB, Metric.DIRECTION_NONE, true, DOMAIN_DESIGN) + .setFormula(new SumChildDistributionFormula().setMinimumScopeToPersist(Scopes.DIRECTORY)); public static final String LCOM4_KEY = "lcom4"; public static final Metric LCOM4 = new Metric(LCOM4_KEY, "LCOM4", "Lack of Cohesion of Methods", Metric.ValueType.FLOAT, @@ -436,7 +438,7 @@ public final class CoreMetrics { public static final String LCOM4_DISTRIBUTION_KEY = "lcom4_distribution"; public static final Metric LCOM4_DISTRIBUTION = new Metric(LCOM4_DISTRIBUTION_KEY, "Class distribution /LCOM4", "Class distribution /LCOM4", Metric.ValueType.DISTRIB, Metric.DIRECTION_NONE, true, DOMAIN_DESIGN) - .setFormula(new SumChildDistributionFormula()); + .setFormula(new SumChildDistributionFormula().setMinimumScopeToPersist(Scopes.DIRECTORY)); public static final String SUSPECT_LCOM4_DENSITY_KEY = "suspect_lcom4_density"; public static final Metric SUSPECT_LCOM4_DENSITY = new Metric(SUSPECT_LCOM4_DENSITY_KEY, "Suspect LCOM4 density", diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/SumChildDistributionFormula.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/SumChildDistributionFormula.java index 423d2df83e9..d80b78ddc12 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/SumChildDistributionFormula.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/SumChildDistributionFormula.java @@ -19,9 +19,11 @@ */ package org.sonar.api.measures; -import java.util.List; -import java.util.Collections; +import org.sonar.api.resources.Scopes; + import java.util.Collection; +import java.util.Collections; +import java.util.List; /** * @since 2.0 @@ -30,10 +32,21 @@ import java.util.Collection; */ public class SumChildDistributionFormula implements Formula { + private String minimumScopeToPersist= Scopes.FILE; + public List dependsUponMetrics() { return Collections.emptyList(); } + public String getMinimumScopeToPersist() { + return minimumScopeToPersist; + } + + public SumChildDistributionFormula setMinimumScopeToPersist(String s) { + this.minimumScopeToPersist = s; + return this; + } + public Measure calculate(FormulaData data, FormulaContext context) { Collection measures = data.getChildrenMeasures(context.getTargetMetric()); if (measures == null || measures.isEmpty()) { @@ -44,7 +57,11 @@ public class SumChildDistributionFormula implements Formula { for (Measure measure : measures) { distribution.add(measure); } - return distribution.build(); + Measure measure = distribution.build(); + if (!Scopes.isHigherThanOrEquals(context.getResource().getScope(), minimumScopeToPersist)) { + measure.setPersistenceMode(PersistenceMode.MEMORY); + } + return measure; } } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/resources/BlockUnit.java b/sonar-plugin-api/src/main/java/org/sonar/api/resources/BlockUnit.java new file mode 100644 index 00000000000..ee6bbe1ac03 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/resources/BlockUnit.java @@ -0,0 +1,104 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.api.resources; + +public class BlockUnit extends Resource { + + public static final String SCOPE = Scopes.BLOCK_UNIT; + + protected String qualifier; + protected Language language; + + protected BlockUnit(String key, String qualifier, Language language) { + setKey(key); + this.qualifier = qualifier; + this.language = language; + } + + @Override + public String getName() { + return getKey(); + } + + @Override + public String getLongName() { + return getKey(); + } + + @Override + public String getDescription() { + return null; + } + + @Override + public final Language getLanguage() { + return language; + } + + @Override + public final String getScope() { + return SCOPE; + } + + @Override + public final String getQualifier() { + return qualifier; + } + + @Override + public Resource getParent() { + return null; + } + + @Override + public final boolean matchFilePattern(String antPattern) { + return false; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + BlockUnit blockUnit = (BlockUnit) o; + if (!qualifier.equals(blockUnit.qualifier)) { + return false; + } + return getKey().equals(blockUnit.getKey()); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + getKey().hashCode(); + result = 31 * result + qualifier.hashCode(); + return result; + } + + public static BlockUnit createMethod(String key, Language language) { + return new BlockUnit(key, Qualifiers.METHOD, language); + } +} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/measures/SumChildDistributionFormulaTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/measures/SumChildDistributionFormulaTest.java index 9849c28d035..93d89b98abb 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/measures/SumChildDistributionFormulaTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/measures/SumChildDistributionFormulaTest.java @@ -22,6 +22,7 @@ package org.sonar.api.measures; import com.google.common.collect.Lists; import org.junit.Before; import org.junit.Test; +import org.sonar.api.resources.File; import java.util.Collections; import java.util.List; @@ -42,6 +43,7 @@ public class SumChildDistributionFormulaTest { public void init() { formula = new SumChildDistributionFormula(); context = mock(FormulaContext.class); + when(context.getResource()).thenReturn(new File("foo")); data = mock(FormulaData.class); } diff --git a/tests/integration/sonar-it-reference-plugin/src/main/java/itests/ExcludedResourceFilter.java b/tests/integration/sonar-it-reference-plugin/src/main/java/itests/ExcludedResourceFilter.java new file mode 100644 index 00000000000..4c045cdb3b9 --- /dev/null +++ b/tests/integration/sonar-it-reference-plugin/src/main/java/itests/ExcludedResourceFilter.java @@ -0,0 +1,13 @@ +package itests; + +import org.apache.commons.lang.StringUtils; +import org.sonar.api.batch.ResourceFilter; +import org.sonar.api.resources.Resource; +import org.sonar.api.resources.Scopes; + +public final class ExcludedResourceFilter implements ResourceFilter { + + public boolean isIgnored(Resource resource) { + return Scopes.isFile(resource) && StringUtils.contains(resource.getName(), "ExcludedByFilter"); + } +} diff --git a/tests/integration/sonar-it-reference-plugin/src/main/java/itests/ITestsPlugin.java b/tests/integration/sonar-it-reference-plugin/src/main/java/itests/ITestsPlugin.java index 6df7c15befb..5fd5809f3b3 100644 --- a/tests/integration/sonar-it-reference-plugin/src/main/java/itests/ITestsPlugin.java +++ b/tests/integration/sonar-it-reference-plugin/src/main/java/itests/ITestsPlugin.java @@ -50,6 +50,7 @@ public class ITestsPlugin implements Plugin { extensions.add(SampleSensor.class); extensions.add(LanguageWithoutRulesEngine.class); extensions.add(ServerSideExtensionUsingExternalDependency.class); + extensions.add(ExcludedResourceFilter.class); // web extensions.add(SampleResourceTab.class); diff --git a/tests/integration/tests/maven-projects/java-complexity/pom.xml b/tests/integration/tests/maven-projects/java-complexity/pom.xml new file mode 100644 index 00000000000..63afab1e8ab --- /dev/null +++ b/tests/integration/tests/maven-projects/java-complexity/pom.xml @@ -0,0 +1,8 @@ + + 4.0.0 + org.sonar.tests + java-complexity + 1.0-SNAPSHOT + java-complexity + \ No newline at end of file diff --git a/tests/integration/tests/maven-projects/java-complexity/src/main/java/foo/ContainsInnerClasses.java b/tests/integration/tests/maven-projects/java-complexity/src/main/java/foo/ContainsInnerClasses.java new file mode 100644 index 00000000000..9ed05abd626 --- /dev/null +++ b/tests/integration/tests/maven-projects/java-complexity/src/main/java/foo/ContainsInnerClasses.java @@ -0,0 +1,37 @@ +package foo; + + +public class ContainsInnerClasses { + + // complexity: 1 + public ContainsInnerClasses() { + + } + + // complexity: 3 + public static class InnerClass { + private String field; + + // complexity: 1 + public InnerClass() { + + } + + // complexity: 2 + public InnerClass(String s) { + if (s != null) { + field = s; + } + } + } +} + +// complexity: 1 +class PackageClass { + private String field; + + // complexity: 1 + public PackageClass() { + + } + } diff --git a/tests/integration/tests/maven-projects/java-complexity/src/main/java/foo/ExcludedByFilter.java b/tests/integration/tests/maven-projects/java-complexity/src/main/java/foo/ExcludedByFilter.java new file mode 100644 index 00000000000..b5193444cd6 --- /dev/null +++ b/tests/integration/tests/maven-projects/java-complexity/src/main/java/foo/ExcludedByFilter.java @@ -0,0 +1,19 @@ +package foo; + +// this class is excluded by the resource filter defined in sonar-it-reference-plugin +public class ExcludedByFilter { + + public void say() { + int i=0; + if(i>5) { + System.out.println("say something"); + } + } + + public void cry() { + int i=0; + if(i<5) { + System.out.println("cry"); + } + } +} diff --git a/tests/integration/tests/maven-projects/java-complexity/src/main/java/foo/Helloworld.java b/tests/integration/tests/maven-projects/java-complexity/src/main/java/foo/Helloworld.java new file mode 100644 index 00000000000..37fae842697 --- /dev/null +++ b/tests/integration/tests/maven-projects/java-complexity/src/main/java/foo/Helloworld.java @@ -0,0 +1,42 @@ +package foo; + +// complexity: 6 +public class Helloworld { + + private String field = null; + + // this is considered as a method + // complexity: 2 + static { + int i = 0; + if (i > 5) { + System.out.println("hello from static block"); + } + } + + // complexity: 1 + public Helloworld(String s) { + this.field = s; + } + + // accessor + // complexity: 0 + public String getField() { + return field; + } + + // accessor + // complexity: 0 + public void setField(String s) { + this.field = s; + } + + // complexity: 3 + public void sayHello() { + for (int i = 0; i < 5; i++) { + if (field != null) { + System.out.println(field); + } + } + } +} diff --git a/tests/integration/tests/src/test/java/org/sonar/tests/integration/JavaComplexityIT.java b/tests/integration/tests/src/test/java/org/sonar/tests/integration/JavaComplexityIT.java new file mode 100644 index 00000000000..e3e3ad1f157 --- /dev/null +++ b/tests/integration/tests/src/test/java/org/sonar/tests/integration/JavaComplexityIT.java @@ -0,0 +1,107 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2009 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.tests.integration; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.wsclient.Sonar; +import org.sonar.wsclient.services.Measure; +import org.sonar.wsclient.services.ResourceQuery; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.nullValue; +import static org.junit.Assert.assertThat; + +public class JavaComplexityIT { + private static Sonar sonar; + + @BeforeClass + public static void buildServer() { + sonar = ITUtils.createSonarWsClient(); + } + + @Test + public void testFileComplexity() { + assertThat(getMeasure("org.sonar.tests:java-complexity:foo.Helloworld", CoreMetrics.COMPLEXITY_KEY).getIntValue(), is(6)); + assertThat(getMeasure("org.sonar.tests:java-complexity:foo.ContainsInnerClasses", CoreMetrics.COMPLEXITY_KEY).getIntValue(), is(5)); + } + + @Test + public void testPackageComplexity() { + assertThat(getMeasure("org.sonar.tests:java-complexity:foo", CoreMetrics.COMPLEXITY_KEY).getIntValue(), is(11)); + } + + @Test + public void testProjectComplexity() { + assertThat(getMeasure("org.sonar.tests:java-complexity", CoreMetrics.COMPLEXITY_KEY).getIntValue(), is(11)); + } + + @Test + public void testAverageMethodComplexity() { + // complexity 6 / 2 methods + // BUG http://jira.codehaus.org/browse/SONAR-2152 + // => the complexity of the static block should not be included. Good value should be 4 / 2 = 2 + assertThat(getMeasure("org.sonar.tests:java-complexity:foo.Helloworld", CoreMetrics.FUNCTION_COMPLEXITY_KEY).getValue(), is(3.0)); + + // complexity 5 / 4 methods. Real value is 1.25 but round up to 1.3 + assertThat(getMeasure("org.sonar.tests:java-complexity:foo.ContainsInnerClasses", CoreMetrics.FUNCTION_COMPLEXITY_KEY).getValue(), is(1.3)); + + // 3.0 * 2 + 1.25 * 4 = 11 for 6 methods + assertThat(getMeasure("org.sonar.tests:java-complexity:foo", CoreMetrics.FUNCTION_COMPLEXITY_KEY).getValue(), is(1.8)); + assertThat(getMeasure("org.sonar.tests:java-complexity", CoreMetrics.FUNCTION_COMPLEXITY_KEY).getValue(), is(1.8)); + } + + @Test + public void testAverageClassComplexity() { + assertThat(getMeasure("org.sonar.tests:java-complexity:foo.Helloworld", CoreMetrics.CLASS_COMPLEXITY_KEY).getValue(), is(6.0)); + + // 1 + 1 + 3 => complexity 5/3 + assertThat(getMeasure("org.sonar.tests:java-complexity:foo.ContainsInnerClasses", CoreMetrics.CLASS_COMPLEXITY_KEY).getValue(), is(1.7)); + + // 1 + 1 + 3 + 6 => 11/4 = 2.75 + assertThat(getMeasure("org.sonar.tests:java-complexity:foo", CoreMetrics.CLASS_COMPLEXITY_KEY).getValue(), is(2.8)); + } + + @Test + public void testDistributionOfClassComplexity() { + // 1 + 1 + 3 + 6 => 3 in range [0,5[ and 1 in range [5,10[ + assertThat(getMeasure("org.sonar.tests:java-complexity:foo", CoreMetrics.CLASS_COMPLEXITY_DISTRIBUTION_KEY).getData(), is("0=3;5=1;10=0;20=0;30=0;60=0;90=0")); + assertThat(getMeasure("org.sonar.tests:java-complexity", CoreMetrics.CLASS_COMPLEXITY_DISTRIBUTION_KEY).getData(), is("0=3;5=1;10=0;20=0;30=0;60=0;90=0")); + } + + @Test + public void testDistributionOfMethodComplexity() { + // ContainsInnerClasses: 1+ 1 + 2 + 1 + // Helloworld: 1 + 3 (static block is not a method) + assertThat(getMeasure("org.sonar.tests:java-complexity:foo", CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION_KEY).getData(), is("1=4;2=2;4=0;6=0;8=0;10=0;12=0")); + assertThat(getMeasure("org.sonar.tests:java-complexity", CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION_KEY).getData(), is("1=4;2=2;4=0;6=0;8=0;10=0;12=0")); + } + + @Test + public void shouldNotPersistDistributionOnFiles() { + assertThat(getMeasure("org.sonar.tests:java-complexity:foo.Helloworld", CoreMetrics.CLASS_COMPLEXITY_DISTRIBUTION_KEY), nullValue()); + assertThat(getMeasure("org.sonar.tests:java-complexity:foo.Helloworld", CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION_KEY), nullValue()); + } + + private Measure getMeasure(String resourceKey, String metricKey) { + return sonar.find(ResourceQuery.createForMetrics(resourceKey, metricKey)).getMeasure(metricKey); + } +} diff --git a/tests/integration/tests/src/test/java/org/sonar/tests/integration/Struts139IT.java b/tests/integration/tests/src/test/java/org/sonar/tests/integration/Struts139IT.java index bcffec6f59a..2724b24db7f 100644 --- a/tests/integration/tests/src/test/java/org/sonar/tests/integration/Struts139IT.java +++ b/tests/integration/tests/src/test/java/org/sonar/tests/integration/Struts139IT.java @@ -129,7 +129,23 @@ public class Struts139IT { assertThat(getProjectMeasure("statements").getIntValue(), is(21896)); assertThat(getProjectMeasure("class_complexity").getValue(), is(21.5)); assertThat(getProjectMeasure("function_complexity").getValue(), is(2.6)); - assertThat(getProjectMeasure("class_complexity_distribution").getData(), is("0=172;5=90;10=86;20=55;30=69;60=34;90=17")); + } + + @Test + public void classComplexityDistribution() throws Exception { + assertThat(sonar.find(ResourceQuery.createForMetrics("org.apache.struts:struts-core:org.apache.struts.config", "class_complexity_distribution")).getMeasure("class_complexity_distribution").getData(), is("0=10;5=3;10=2;20=1;30=4;60=4;90=1")); + assertThat(getCoreModuleMeasure("class_complexity_distribution").getData(), is("0=49;5=26;10=24;20=14;30=18;60=9;90=10")); + assertThat(getProjectMeasure("class_complexity_distribution").getData(), is("0=173;5=90;10=86;20=55;30=69;60=34;90=17")); + } + + @Test + public void functionComplexityDistribution() throws Exception { + assertThat(sonar.find(ResourceQuery.createForMetrics("org.apache.struts:struts-core:org.apache.struts.config", "function_complexity_distribution")).getMeasure("function_complexity_distribution").getData(), is("1=186;2=88;4=11;6=12;8=7;10=2;12=8")); + } + + @Test + public void shouldNotPersistComplexityDistributionsOnFiles() { + assertThat(sonar.find(ResourceQuery.createForMetrics("org.apache.struts:struts-core:org.apache.struts.config.ConfigRuleSet", "function_complexity_distribution", "class_complexity_distribution")).getMeasures().size(), is(0)); } @Test -- 2.39.5