diff options
author | GAUDIN <gaudol@gmail.com> | 2011-09-30 16:31:22 +0200 |
---|---|---|
committer | GAUDIN <gaudol@gmail.com> | 2011-09-30 16:31:22 +0200 |
commit | b5016f504b3a7a29654e41c19ca78da2c8fa3be6 (patch) | |
tree | c61c40da1dd802fc974ee7cb441d7924236faf01 /plugins | |
parent | 43f27118331e8395365562c3859986489e38fb0a (diff) | |
parent | 520ebfd35bc581fa12c8e8267a548dc4d56317c6 (diff) | |
download | sonarqube-2.11.tar.gz sonarqube-2.11.zip |
Merge branch Release-2.112.11
Diffstat (limited to 'plugins')
55 files changed, 1836 insertions, 283 deletions
diff --git a/plugins/sonar-checkstyle-plugin/pom.xml b/plugins/sonar-checkstyle-plugin/pom.xml index 98a1d6d1a2b..07594f143b7 100644 --- a/plugins/sonar-checkstyle-plugin/pom.xml +++ b/plugins/sonar-checkstyle-plugin/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar</artifactId> - <version>2.10</version> + <version>2.11</version> <relativePath>../..</relativePath> </parent> <groupId>org.codehaus.sonar.plugins</groupId> diff --git a/plugins/sonar-cobertura-plugin/pom.xml b/plugins/sonar-cobertura-plugin/pom.xml index 80ec7f34ff2..57650329f4f 100644 --- a/plugins/sonar-cobertura-plugin/pom.xml +++ b/plugins/sonar-cobertura-plugin/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar</artifactId> - <version>2.10</version> + <version>2.11</version> <relativePath>../..</relativePath> </parent> <groupId>org.codehaus.sonar.plugins</groupId> diff --git a/plugins/sonar-core-gwt/pom.xml b/plugins/sonar-core-gwt/pom.xml index 6ca87684f3e..c763fef63d2 100644 --- a/plugins/sonar-core-gwt/pom.xml +++ b/plugins/sonar-core-gwt/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar</artifactId> - <version>2.10</version> + <version>2.11</version> <relativePath>../..</relativePath> </parent> <artifactId>sonar-core-gwt</artifactId> diff --git a/plugins/sonar-core-gwt/src/main/java/org/sonar/plugins/core/duplicationsviewer/client/DuplicationsPanel.java b/plugins/sonar-core-gwt/src/main/java/org/sonar/plugins/core/duplicationsviewer/client/DuplicationsPanel.java index cad1b96cbc3..681205f4679 100644 --- a/plugins/sonar-core-gwt/src/main/java/org/sonar/plugins/core/duplicationsviewer/client/DuplicationsPanel.java +++ b/plugins/sonar-core-gwt/src/main/java/org/sonar/plugins/core/duplicationsviewer/client/DuplicationsPanel.java @@ -91,13 +91,15 @@ public class DuplicationsPanel extends Composite { panel.add(table); int rowCounter = 1; + + String projectKey = resource.getKey().substring(0, resource.getKey().lastIndexOf(':')); for (int i = 0; i < duplicationsXML.getLength(); i++) { Element duplicationXML = (Element) duplicationsXML.item(i); String lines = duplicationXML.getAttribute("lines"); String startLine = duplicationXML.getAttribute("start"); String targetStartLine = duplicationXML.getAttribute("target-start"); String targetResourceKey = duplicationXML.getAttribute("target-resource"); - renderDuplication(rowCounter, i, table, lines, startLine, targetStartLine, targetResourceKey, resource); + renderDuplication(rowCounter, i, table, lines, startLine, targetStartLine, targetResourceKey, resource, projectKey); rowCounter+=2; } } @@ -121,7 +123,7 @@ public class DuplicationsPanel extends Composite { return table; } - private void renderDuplication(int row, int duplicationCounter, FlexTable table, String lines, String startLine, String targetStartLine, String targetResourceKey, final Resource resource) { + private void renderDuplication(int row, int duplicationCounter, FlexTable table, String lines, String startLine, String targetStartLine, String targetResourceKey, final Resource resource, String projectKey) { String style = (duplicationCounter % 2 == 0) ? "odd" : "even"; SourcePanel src = new DefaultSourcePanel(resource, new Integer(startLine), new Integer(lines)); @@ -137,9 +139,18 @@ public class DuplicationsPanel extends Composite { targetResourceKey = "Same file"; } if (targetResourceKey.contains(":")) { - targetResourceKey = targetResourceKey.substring(targetResourceKey.lastIndexOf(':') + 1); + int i = targetResourceKey.lastIndexOf(':'); + String targetProjectKey = targetResourceKey.substring(0, i); + String targetFileKey = targetResourceKey.substring(i + 1); + if (targetProjectKey.equals(projectKey)) { + // same project + targetResourceKey = targetFileKey; + } else { + // another project + targetResourceKey = targetProjectKey + "<br/>" + targetFileKey; + } } - table.setText(row, 3, targetResourceKey); + table.setHTML(row, 3, targetResourceKey); table.setText(row, 4, targetStartLine); setRowStyle(row, table, style, false); diff --git a/plugins/sonar-core-gwt/src/main/java/org/sonar/plugins/core/hotspots/client/widget/MostViolatedRules.java b/plugins/sonar-core-gwt/src/main/java/org/sonar/plugins/core/hotspots/client/widget/MostViolatedRules.java index 44d08372033..4c707e6f327 100644 --- a/plugins/sonar-core-gwt/src/main/java/org/sonar/plugins/core/hotspots/client/widget/MostViolatedRules.java +++ b/plugins/sonar-core-gwt/src/main/java/org/sonar/plugins/core/hotspots/client/widget/MostViolatedRules.java @@ -91,7 +91,7 @@ public class MostViolatedRules extends AbstractHotspot { @Override protected void doOnResponse(Resource resource) { - if (resource.getMeasures().isEmpty()) { + if (resource==null || resource.getMeasures().isEmpty()) { renderEmptyResults(); } else { renderGrid(resource); @@ -133,9 +133,9 @@ public class MostViolatedRules extends AbstractHotspot { .setDepth(0) .setExcludeRules(false) .setLimit(LIMIT); - String priority = getSelectedPriority(); - if (priority!=null) { - query.setRulePriorities(priority); + String severity = getSelectedPriority(); + if (severity!=null) { + query.setRuleSeverities(severity); } return query; } diff --git a/plugins/sonar-core-plugin/pom.xml b/plugins/sonar-core-plugin/pom.xml index ad47309db53..c69d5e8170b 100644 --- a/plugins/sonar-core-plugin/pom.xml +++ b/plugins/sonar-core-plugin/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar</artifactId> - <version>2.10</version> + <version>2.11</version> <relativePath>../..</relativePath> </parent> <groupId>org.codehaus.sonar.plugins</groupId> diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java index 97b8e06428c..354e00c9e37 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java @@ -45,12 +45,21 @@ import java.util.List; @Properties({ @Property( + key = CoreProperties.SERVER_BASE_URL, + defaultValue = CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE, + name = "Server base URL", + description = "HTTP URL of this Sonar server, such as <i>http://yourhost.yourdomain/sonar</i>. This value is used i.e. to create links in emails.", + project = false, + global = true, + category = CoreProperties.CATEGORY_GENERAL), + @Property( key = CoreProperties.CORE_COVERAGE_PLUGIN_PROPERTY, defaultValue = "cobertura", name = "Code coverage plugin", description = "Key of the code coverage plugin to use.", project = true, - global = true), + global = true, + category = CoreProperties.CATEGORY_CODE_COVERAGE), @Property( key = CoreProperties.CORE_IMPORT_SOURCES_PROPERTY, defaultValue = "" + CoreProperties.CORE_IMPORT_SOURCES_DEFAULT_VALUE, @@ -58,14 +67,16 @@ import java.util.List; description = "Set to false if sources should not be displayed, e.g. for security reasons.", project = true, module = true, - global = true), + global = true, + category = CoreProperties.CATEGORY_SECURITY), @Property( key = CoreProperties.CORE_TENDENCY_DEPTH_PROPERTY, defaultValue = "" + CoreProperties.CORE_TENDENCY_DEPTH_DEFAULT_VALUE, name = "Tendency period", description = TendencyDecorator.PROP_DAYS_DESCRIPTION, project = false, - global = true), + global = true, + category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), @Property( key = CoreProperties.SKIP_TENDENCIES_PROPERTY, defaultValue = "" + CoreProperties.SKIP_TENDENCIES_DEFAULT_VALUE, @@ -73,56 +84,55 @@ import java.util.List; description = "Skip calculation of measure tendencies", project = true, module = false, - global = true), + global = true, + category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), @Property( key = CoreProperties.CORE_SKIPPED_MODULES_PROPERTY, name = "Exclude modules", description = "Maven artifact ids of modules to exclude (comma-separated).", project = true, - global = false), + global = false, + category = CoreProperties.CATEGORY_GENERAL), @Property( key = CoreProperties.CORE_RULE_WEIGHTS_PROPERTY, defaultValue = CoreProperties.CORE_RULE_WEIGHTS_DEFAULT_VALUE, name = "Rules weight", description = "A weight is associated to each priority to calculate the Rules Compliance Index.", project = false, - global = true), - @Property( - key = CoreProperties.SERVER_BASE_URL, - defaultValue = CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE, - name = "Server base URL", - description = "HTTP address of the Sonar server, such as <i>http://yourhost.yourdomain/sonar</i>. This value is used i.e. to create links in emails.", - project = false, - global = true), + global = true, + category = CoreProperties.CATEGORY_GENERAL), @Property( key = CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY, defaultValue = "" + CoreProperties.CORE_FORCE_AUTHENTICATION_DEFAULT_VALUE, name = "Force user authentication", description = "Forcing user authentication stops un-logged users to access Sonar.", project = false, - global = true), + global = true, + category = CoreProperties.CATEGORY_SECURITY), @Property( key = CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_PROPERTY, defaultValue = "" + CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_DEAULT_VALUE, name = "Allow users to sign up online", description = "Users can sign up online.", project = false, - global = true), + global = true, + category = CoreProperties.CATEGORY_SECURITY), @Property( key = CoreProperties.CORE_DEFAULT_GROUP, defaultValue = CoreProperties.CORE_DEFAULT_GROUP_DEFAULT_VALUE, name = "Default user group", description = "Any new users will automatically join this group.", project = false, - global = true - ), + global = true, + category = CoreProperties.CATEGORY_SECURITY), @Property( key = CoreProperties.CORE_VIOLATION_LOCALE_PROPERTY, defaultValue = "en", name = "Locale used for violation messages", description = "Locale to be used when generating violation messages. It's up to each rule engine to support this global internationalization property", project = true, - global = true), + global = true, + category = CoreProperties.CATEGORY_L10N), @Property( key = "sonar.timemachine.period1", name = "Period 1", @@ -131,24 +141,24 @@ import java.util.List; "compare to previous analysis</li><li>A version, for example 1.2</li></ul>", project = false, global = true, - defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_1 - ), + defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_1, + category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), @Property( key = "sonar.timemachine.period2", name = "Period 2", description = "See the property 'Period 1'", project = false, global = true, - defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_2 - ), + defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_2, + category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), @Property( key = "sonar.timemachine.period3", name = "Period 3", description = "See the property 'Period 1'", project = false, global = true, - defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_3 - ), + defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_3, + category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), @Property( key = "sonar.timemachine.period4", name = "Period 4", @@ -157,16 +167,16 @@ import java.util.List; "for example 2010-12-25</li><li>'previous_analysis' to compare to previous analysis</li><li>A version, for example 1.2</li></ul>", project = true, global = false, - defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_4 - ), + defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_4, + category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS), @Property( key = "sonar.timemachine.period5", name = "Period 5", description = "See the property 'Period 4'", project = true, global = false, - defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_5 - ) + defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_5, + category = CoreProperties.CATEGORY_DIFFERENTIAL_VIEWS) }) public class CorePlugin extends SonarPlugin { @@ -197,6 +207,8 @@ public class CorePlugin extends SonarPlugin { extensions.add(SizeWidget.class); extensions.add(EventsWidget.class); extensions.add(CustomMeasuresWidget.class); + extensions.add(TimelineWidget.class); + extensions.add(TimeMachineWidget.class); // chart extensions.add(XradarChart.class); diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/batch/ExcludedResourceFilter.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/batch/ExcludedResourceFilter.java index f594d194835..f7bc914d5fe 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/batch/ExcludedResourceFilter.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/batch/ExcludedResourceFilter.java @@ -19,8 +19,9 @@ */ package org.sonar.plugins.core.batch; +import org.apache.commons.configuration.Configuration; +import org.sonar.api.CoreProperties; import org.sonar.api.batch.ResourceFilter; -import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.api.resources.ResourceUtils; @@ -29,14 +30,10 @@ import org.sonar.api.resources.ResourceUtils; */ public class ExcludedResourceFilter implements ResourceFilter { - private String[] exclusionPatterns; + private Configuration conf; - public ExcludedResourceFilter(Project project) { - this(project.getExclusionPatterns()); - } - - protected ExcludedResourceFilter(String[] exclusionPatterns) { - this.exclusionPatterns = (exclusionPatterns==null ? new String[0] : exclusionPatterns); + public ExcludedResourceFilter(Configuration conf) { + this.conf = conf; } public boolean isIgnored(Resource resource) { @@ -45,11 +42,18 @@ public class ExcludedResourceFilter implements ResourceFilter { return false; } - for (String pattern : exclusionPatterns) { - if (resource.matchFilePattern(pattern)) { - return true; + String[] patterns = getExclusionPatterns(); + if (patterns != null) { + for (String pattern : patterns) { + if (resource.matchFilePattern(pattern)) { + return true; + } } } return false; } + + String[] getExclusionPatterns() { + return conf.getStringArray(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY); + } }
\ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java index 81e93fe6f1b..67ca1ec63c5 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java @@ -116,6 +116,16 @@ public class ViolationTrackingDecorator implements Decorator { pastViolationsByRule, referenceViolationsMap); } } + + // Last check: match violation if same rule and same checksum but different line and different message + // See https://jira.codehaus.org/browse/SONAR-2812 + for (Violation newViolation : newViolations) { + if (isNotAlreadyMapped(newViolation, referenceViolationsMap)) { + mapViolation(newViolation, + findPastViolationWithSameChecksum(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), + pastViolationsByRule, referenceViolationsMap); + } + } } return referenceViolationsMap; } @@ -124,6 +134,15 @@ public class ViolationTrackingDecorator implements Decorator { return !violationMap.containsKey(newViolation); } + private RuleFailureModel findPastViolationWithSameChecksum(Violation newViolation, Collection<RuleFailureModel> pastViolations) { + for (RuleFailureModel pastViolation : pastViolations) { + if (isSameChecksum(newViolation, pastViolation)) { + return pastViolation; + } + } + return null; + } + private RuleFailureModel findPastViolationWithSameLineAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) { for (RuleFailureModel pastViolation : pastViolations) { if (isSameLine(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) { diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TimeMachineWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TimeMachineWidget.java new file mode 100644 index 00000000000..04fc6d670c0 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TimeMachineWidget.java @@ -0,0 +1,59 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * 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.core.widgets; + +import org.sonar.api.web.AbstractRubyTemplate; +import org.sonar.api.web.RubyRailsWidget; +import org.sonar.api.web.WidgetCategory; +import org.sonar.api.web.WidgetProperties; +import org.sonar.api.web.WidgetProperty; +import org.sonar.api.web.WidgetPropertyType; + +@WidgetCategory({ "History" }) +@WidgetProperties( + { + @WidgetProperty(key = "numberOfColumns", type = WidgetPropertyType.INTEGER, defaultValue = "4"), + @WidgetProperty(key = "displaySparkLine", type = WidgetPropertyType.BOOLEAN), + @WidgetProperty(key = "metric1", type = WidgetPropertyType.METRIC, defaultValue = "ncloc"), + @WidgetProperty(key = "metric2", type = WidgetPropertyType.METRIC), + @WidgetProperty(key = "metric3", type = WidgetPropertyType.METRIC), + @WidgetProperty(key = "metric4", type = WidgetPropertyType.METRIC), + @WidgetProperty(key = "metric5", type = WidgetPropertyType.METRIC), + @WidgetProperty(key = "metric6", type = WidgetPropertyType.METRIC), + @WidgetProperty(key = "metric7", type = WidgetPropertyType.METRIC), + @WidgetProperty(key = "metric8", type = WidgetPropertyType.METRIC), + @WidgetProperty(key = "metric9", type = WidgetPropertyType.METRIC), + @WidgetProperty(key = "metric10", type = WidgetPropertyType.METRIC) + } +) +public class TimeMachineWidget extends AbstractRubyTemplate implements RubyRailsWidget { + public String getId() { + return "time_machine"; + } + + public String getTitle() { + return "History Table"; + } + + @Override + protected String getTemplatePath() { + return "/org/sonar/plugins/core/widgets/time_machine.html.erb"; + } +}
\ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TimelineWidget.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TimelineWidget.java new file mode 100644 index 00000000000..e49d1c9393f --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/widgets/TimelineWidget.java @@ -0,0 +1,53 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * 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.core.widgets; + +import org.sonar.api.web.AbstractRubyTemplate; +import org.sonar.api.web.RubyRailsWidget; +import org.sonar.api.web.WidgetCategory; +import org.sonar.api.web.WidgetProperties; +import org.sonar.api.web.WidgetProperty; +import org.sonar.api.web.WidgetPropertyType; + +@WidgetCategory({ "History" }) +@WidgetProperties( + { + @WidgetProperty(key = "chartTitle", type = WidgetPropertyType.STRING), + @WidgetProperty(key = "metric1", type = WidgetPropertyType.METRIC, defaultValue = "ncloc"), + @WidgetProperty(key = "metric2", type = WidgetPropertyType.METRIC), + @WidgetProperty(key = "metric3", type = WidgetPropertyType.METRIC), + @WidgetProperty(key = "hideEvents", type = WidgetPropertyType.BOOLEAN), + @WidgetProperty(key = "chartHeight", type = WidgetPropertyType.INTEGER, defaultValue = "80") + } +) +public class TimelineWidget extends AbstractRubyTemplate implements RubyRailsWidget { + public String getId() { + return "timeline"; + } + + public String getTitle() { + return "Timeline"; + } + + @Override + protected String getTemplatePath() { + return "/org/sonar/plugins/core/widgets/timeline.html.erb"; + } +}
\ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/custom_measures.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/custom_measures.html.erb index bc6c5d5b94e..e2ed71ea368 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/custom_measures.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/custom_measures.html.erb @@ -1,19 +1,25 @@ -<% -(1..10).each do |index| - metric=widget_properties["metric#{index}"] - if metric - m=measure(metric) - if m -%> - <div class="dashbox"> - <p class="title"><%= metric.short_name -%></p> - <p> - <span class="big"><%= format_measure(m, :url => url_for_drilldown(m)) -%></span> - <%= dashboard_configuration.selected_period? ? format_variation(m) : trend_icon(m) -%> - </p> - </div> -<% - end - end -end -%>
\ No newline at end of file +<table class="width100"> + <tr> + <td width="100%"> + <% + (1..10).each do |index| + metric=widget_properties["metric#{index}"] + if metric + m=measure(metric) + if m + %> + <div class="dashbox"> + <p class="title"><%= metric.short_name -%></p> + <p> + <span class="big"><%= format_measure(m, :url => url_for_drilldown(m)) -%></span> + <%= dashboard_configuration.selected_period? ? format_variation(m) : trend_icon(m) -%> + </p> + </div> + <% + end + end + end + %> + </td> + </tr> +</table>
\ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/time_machine.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/time_machine.html.erb new file mode 100644 index 00000000000..b8d8bcce8a8 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/time_machine.html.erb @@ -0,0 +1,111 @@ +<% + # Retrieve widget settings + metric_ids = [] + (1..10).each do |index| + metric=widget_properties["metric#{index}"] + if metric + metric_ids << metric.id + end + end + if metric_ids.empty? + # No metric has been selected, it's the first time the widget is displayed: 'ncloc' is the default metric + ncloc = Metric.find(:first, :conditions => "name = 'ncloc'") + metric_ids << ncloc.id + end + numberOfColumns = widget_properties["numberOfColumns"].to_i == 0 ? 4 : widget_properties["numberOfColumns"].to_i + displaySparkLine = widget_properties["displaySparkLine"] + + # Retrieve the measures for each metric on each snapshot + options = {} + from_date = dashboard_configuration.from_datetime + if from_date + options[:from] = from_date + end + snapshots=Snapshot.for_timemachine_widget(@resource, numberOfColumns, options) + sids = snapshots.collect{|s| s.id}.uniq + measures=ProjectMeasure.find(:all, :conditions => {:snapshot_id => sids, :metric_id => metric_ids}) + + # And prepare the rows to display + snapshot_by_id={} + snapshots.each do |s| + snapshot_by_id[s.id]=s + end + rows_by_metric_id={} + measures.each do |measure| + next unless measure.metric + + if measure.metric.timemachine? && (measure.value || measure.text_value) + row=rows_by_metric_id[measure.metric_id] + unless row + row=Sonar::TimemachineRow.new(measure.metric) + rows_by_metric_id[measure.metric_id]=row + end + + #optimization : avoid eager loading of snapshots + measure.snapshot=snapshot_by_id[measure.snapshot_id] + row.add_measure(measure) + end + end + + # Create the list of rows to display in the same order as defined by the user + rows=[] + metric_ids.each do |metric_id| + row = rows_by_metric_id[metric_id] + if row + rows<<row + end + end +%> + +<div class="widget-matrix"> + +<table class="data"> + + <thead> + <tr> + <th> </th> + <% + snapshots.each do |snapshot| + event = snapshot.event('Version') + %> + <th nowrap="nowrap" style="vertical-align:top"> + <%= l snapshot.created_at.to_date -%> + <br/> + <%= event.name unless event==nil -%> + </th> + <% end %> + <% if displaySparkLine %> + <th> </th> + <% end %> + </tr> + </thead> + + <tbody> + <% + rows.select{|row| row.metric.val_type != Metric::VALUE_TYPE_DISTRIB}.each do |row| + %> + <tr class="<%= cycle 'even','odd' -%>"> + <td width="1%" nowrap="nowrap" class="left text"> + <%= row.metric.short_name %> + </td> + <% + snapshots.each do |snapshot| + measure=row.measure(snapshot) + %> + <td width="1%" nowrap="nowrap" class="right"><%= format_measure(measure, :skip_span_id => true) %></td> + <% end %> + <% + sparkline_url=row.sparkline_url + if displaySparkLine && sparkline_url + %> + <td width="1%" > + <%= image_tag(sparkline_url) %> + </td> + <% end %> + </tr> + <% end %> + </tbody> + +</table> + +</div>
\ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/timeline.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/timeline.html.erb new file mode 100644 index 00000000000..8926d741323 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/timeline.html.erb @@ -0,0 +1,162 @@ +<% + # Retrieve widget settings + metric_data_map = {} + metric_name_map = {} + (1..3).each do |index| + metric=widget_properties["metric#{index}"] + if metric + metric_data_map[metric.id] = [] + metric_name_map[metric.id] = metric.short_name + end + end + if metric_data_map.empty? + # No metric has been selected, it's the first time the widget is displayed: 'ncloc' is the default metric + ncloc = Metric.find(:first, :conditions => "name = 'ncloc'") + metric_data_map[ncloc.id] = [] + metric_name_map[ncloc.id] = message('metric.ncloc.name') + end + chartHeight = widget_properties["chartHeight"].to_i == 0 ? "null" : widget_properties["chartHeight"] + + # Retrieve metric trend information + options = {} + from_date = dashboard_configuration.from_datetime + if from_date + options[:from] = from_date + end + metric_count_per_snapshot_id = {} + TrendsChart.time_machine_measures(@resource, metric_data_map.keys, options).each() do |trend_item| + sid = trend_item["sid"] + if metric_count_per_snapshot_id[sid] + metric_count_per_snapshot_id[sid] += 1 + else + metric_count_per_snapshot_id[sid] = 1 + end + metric_data_map[trend_item["metric_id"].to_i] << {:date => trend_item["created_at"], :value => trend_item["value"], :sid => trend_item["sid"]} + end + + # Create JS structures to print out in the HTML page + js_data = "[" + js_snapshots = "[" + js_metrics = "[" + total_number_of_metrics = metric_name_map.keys.size() + metric_data_map.keys.each_with_index() do |metric_id, index| + unless metric_data_map[metric_id].empty? + js_metrics += "\"" + metric_name_map[metric_id] + "\"," + js_data += "[" + metric_data_map[metric_id].each() do |metric_data| + # for every metric value, we need to check that the corresponding snapshot has values for each metric (if not, Protovis won't be able to display) + if metric_count_per_snapshot_id[metric_data[:sid]]==total_number_of_metrics + m_date = Time.parse(metric_data[:date]) + js_data += "{x:d(" + js_data += m_date.year.to_s + js_data += "," + # Need to decrease by 1 the month as the JS Date object start months at 0 (= January) + js_data += (m_date.month - 1).to_s + js_data += "," + js_data += m_date.day.to_s + js_data += "," + js_data += m_date.hour.to_s + js_data += "," + js_data += m_date.min.to_s + js_data += "," + js_data += m_date.sec.to_s + js_data += "),y:" + js_data += sprintf( "%0.02f", metric_data[:value]) + js_data += "}," + if index == 0 + # we fill the js_snapshots array (no need to do this more than once) + js_snapshots += "{sid:" + js_snapshots += metric_data[:sid].to_s + js_snapshots += ",d:\"" + js_snapshots += human_short_date m_date + js_snapshots += "\"}," + end + end + end + js_data = js_data.chomp(',') + "]," + end + end + js_data = js_data.chomp(',') + "]" + js_snapshots = js_snapshots.chomp(',') + "]" + js_metrics = js_metrics.chomp(',') + "]" + + # Prepare also event structure if required + unless widget_properties["hideEvents"] + events = {} + unless from_date + # find the oldest date + metric_data_map.values.each() do |metric_data_array| + first_date = Time.parse(metric_data_array[0][:date]) + from_date = first_date if !from_date || from_date > first_date + end + end + Event.find(:all, :conditions => ["resource_id=? AND event_date>=?", @resource.id, from_date], :order => 'event_date').each() do |event| + if events[event.event_date] + events[event.event_date] << event + else + date_entry = [event] + events[event.event_date] = date_entry + end + end + js_events = "[" + events.keys().sort.each() do |e_date| + e_details = events[e_date] + js_events += "{sid:" + js_events += e_details[0].snapshot_id.to_s + js_events += ",d:d(" + js_events += e_date.year.to_s + js_events += "," + # Need to decrease by 1 the month as the JS Date object start months at 0 (= January) + js_events += (e_date.month - 1).to_s + js_events += "," + js_events += e_date.day.to_s + js_events += "," + js_events += e_date.hour.to_s + js_events += "," + js_events += e_date.min.to_s + js_events += "," + js_events += e_date.sec.to_s + js_events += "),l:[" + e_details.each() do |e| + js_events += "{n:\"" + js_events += e.name + js_events += "\"}," + end + js_events = js_events.chomp(',') + "]}," + end + js_events = js_events.chomp(',') + "]" + end + +%> + +<% if widget_properties["chartTitle"] %> +<h3 style="text-align: center; margin-bottom: 10px"><%= h(widget_properties["chartTitle"]) -%></h3> +<% end %> + + +<% if metric_data_map.values[0].size == 1 %> + + <span style="color: #777777; font-size: 93%; font-style:italic"><%= message('widget.timeline.timeline_not_displayed') -%></span> + +<% else %> + + <div id="timeline-chart-<%= widget.id -%>"></div> + <script type="text/javascript+protovis"> + function d(y,m,d,h,min,s) { + return new Date(y,m,d,h,min,s); + } + var data = <%= js_data -%>; + var snapshots = <%= js_snapshots -%>; + var metrics = <%= js_metrics -%>; + var events = <%= js_events ? js_events : "null" -%>; + var timeline = new SonarWidgets.Timeline('timeline-chart-<%= widget.id -%>') + .height(<%= chartHeight -%>) + .data(data) + .snapshots(snapshots) + .metrics(metrics) + .events(events); + timeline.render(); + + </script> + +<% end %>
\ No newline at end of file diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/batch/ExcludedResourceFilterTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/batch/ExcludedResourceFilterTest.java index 2079725ed5e..dbbe8fd9e13 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/batch/ExcludedResourceFilterTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/batch/ExcludedResourceFilterTest.java @@ -19,7 +19,9 @@ */ package org.sonar.plugins.core.batch; +import org.apache.commons.configuration.PropertiesConfiguration; import org.junit.Test; +import org.sonar.api.CoreProperties; import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.Resource; @@ -32,13 +34,16 @@ public class ExcludedResourceFilterTest { @Test public void doNotFailIfNoPatterns() { - ExcludedResourceFilter filter = new ExcludedResourceFilter((String[]) null); + PropertiesConfiguration conf = new PropertiesConfiguration(); + ExcludedResourceFilter filter = new ExcludedResourceFilter(conf); assertThat(filter.isIgnored(mock(Resource.class)), is(false)); } @Test public void noPatternsMatch() { - ExcludedResourceFilter filter = new ExcludedResourceFilter(new String[]{"**/foo/*.java", "**/bar/*"}); + PropertiesConfiguration conf = new PropertiesConfiguration(); + conf.setProperty(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, new String[]{"**/foo/*.java", "**/bar/*"}); + ExcludedResourceFilter filter = new ExcludedResourceFilter(conf); assertThat(filter.isIgnored(mock(Resource.class)), is(false)); } @@ -47,7 +52,9 @@ public class ExcludedResourceFilterTest { */ @Test public void ignoreResourceIfMatchesPattern() { - ExcludedResourceFilter filter = new ExcludedResourceFilter(new String[]{"**/foo/*.java", "**/bar/*"}); + PropertiesConfiguration conf = new PropertiesConfiguration(); + conf.setProperty(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, new String[]{"**/foo/*.java", "**/bar/*"}); + ExcludedResourceFilter filter = new ExcludedResourceFilter(conf); Resource resource = mock(Resource.class); when(resource.matchFilePattern("**/bar/*")).thenReturn(true); @@ -57,7 +64,9 @@ public class ExcludedResourceFilterTest { @Test public void doNotExcludeUnitTestFiles() { - ExcludedResourceFilter filter = new ExcludedResourceFilter(new String[]{"**/foo/*.java", "**/bar/*"}); + PropertiesConfiguration conf = new PropertiesConfiguration(); + conf.setProperty(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, new String[]{"**/foo/*.java", "**/bar/*"}); + ExcludedResourceFilter filter = new ExcludedResourceFilter(conf); Resource unitTest = mock(Resource.class); when(unitTest.getQualifier()).thenReturn(Qualifiers.UNIT_TEST_FILE); diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecoratorTest.java index ba798f14b24..1da2991ece6 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecoratorTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecoratorTest.java @@ -102,6 +102,18 @@ public class ViolationTrackingDecoratorTest { assertThat(mapping.get(newViolation), equalTo(referenceViolation)); } + /** + * See https://jira.codehaus.org/browse/SONAR-2812 + */ + @Test + public void sameChecksumAndRuleButDifferentLineAndDifferentMessage() { + Violation newViolation = newViolation("new message", 1, 50, "checksum1"); + RuleFailureModel referenceViolation = newReferenceViolation("old message", 2, 50, "checksum1"); + + Map<Violation, RuleFailureModel> mapping = decorator.mapViolations(Lists.newArrayList(newViolation), Lists.newArrayList(referenceViolation)); + assertThat(mapping.get(newViolation), equalTo(referenceViolation)); + } + @Test public void shouldCreateNewViolationWhenSameRuleSameMessageButDifferentLineAndChecksum() { Violation newViolation = newViolation("message", 1, 50, "checksum1"); diff --git a/plugins/sonar-cpd-plugin/pom.xml b/plugins/sonar-cpd-plugin/pom.xml index f1859005cce..9cabe551af5 100644 --- a/plugins/sonar-cpd-plugin/pom.xml +++ b/plugins/sonar-cpd-plugin/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar</artifactId> - <version>2.10</version> + <version>2.11</version> <relativePath>../..</relativePath> </parent> <groupId>org.codehaus.sonar.plugins</groupId> @@ -33,6 +33,15 @@ </exclusion> </exclusions> </dependency> + + <!-- For ResourcePersister and database access --> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-batch</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar-duplications</artifactId> diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdAnalyser.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdAnalyser.java index 4214a736039..b50838ae34c 100644 --- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdAnalyser.java +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdAnalyser.java @@ -19,20 +19,21 @@ */ package org.sonar.plugins.cpd; +import java.io.File; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + import net.sourceforge.pmd.cpd.TokenEntry; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.CpdMapping; import org.sonar.api.batch.SensorContext; -import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.measures.Measure; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.duplications.cpd.Match; -import java.io.File; -import java.util.*; - public class CpdAnalyser { private static final Logger LOG = LoggerFactory.getLogger(CpdAnalyser.class); @@ -83,7 +84,7 @@ public class CpdAnalyser { } for (DuplicationsData data : duplicationsData.values()) { - data.saveUsing(context); + data.save(); } } @@ -96,50 +97,4 @@ public class CpdAnalyser { return data; } - private static final class DuplicationsData { - - protected Set<Integer> duplicatedLines = new HashSet<Integer>(); - protected double duplicatedBlocks = 0; - protected Resource resource; - private SensorContext context; - private List<StringBuilder> duplicationXMLEntries = new ArrayList<StringBuilder>(); - - private DuplicationsData(Resource resource, SensorContext context) { - this.context = context; - this.resource = resource; - } - - protected void cumulate(Resource targetResource, int targetDuplicationStartLine, int duplicationStartLine, int duplicatedLines) { - StringBuilder xml = new StringBuilder(); - xml.append("<duplication lines=\"").append(duplicatedLines).append("\" start=\"").append(duplicationStartLine) - .append("\" target-start=\"").append(targetDuplicationStartLine).append("\" target-resource=\"") - .append(context.saveResource(targetResource)).append("\"/>"); - - duplicationXMLEntries.add(xml); - - for (int duplicatedLine = duplicationStartLine; duplicatedLine < duplicationStartLine + duplicatedLines; duplicatedLine++) { - this.duplicatedLines.add(duplicatedLine); - } - } - - protected void incrementDuplicatedBlock() { - duplicatedBlocks++; - } - - protected void saveUsing(SensorContext context) { - context.saveMeasure(resource, CoreMetrics.DUPLICATED_FILES, 1d); - context.saveMeasure(resource, CoreMetrics.DUPLICATED_LINES, (double) duplicatedLines.size()); - context.saveMeasure(resource, CoreMetrics.DUPLICATED_BLOCKS, duplicatedBlocks); - context.saveMeasure(resource, new Measure(CoreMetrics.DUPLICATIONS_DATA, getDuplicationXMLData())); - } - - private String getDuplicationXMLData() { - StringBuilder duplicationXML = new StringBuilder("<duplications>"); - for (StringBuilder xmlEntry : duplicationXMLEntries) { - duplicationXML.append(xmlEntry); - } - duplicationXML.append("</duplications>"); - return duplicationXML.toString(); - } - } } diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java new file mode 100644 index 00000000000..fd1be7bf22e --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java @@ -0,0 +1,38 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * 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.cpd; + +import org.sonar.api.BatchExtension; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.resources.Language; +import org.sonar.api.resources.Project; + +public abstract class CpdEngine implements BatchExtension { + + abstract boolean isLanguageSupported(Language language); + + abstract void analyse(Project project, SensorContext context); + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java index 7a11a8fcd0f..68032599244 100644 --- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java @@ -19,6 +19,9 @@ */ package org.sonar.plugins.cpd; +import java.util.Arrays; +import java.util.List; + import org.sonar.api.CoreProperties; import org.sonar.api.Properties; import org.sonar.api.Property; @@ -26,47 +29,66 @@ import org.sonar.api.SonarPlugin; import org.sonar.plugins.cpd.decorators.DuplicationDensityDecorator; import org.sonar.plugins.cpd.decorators.SumDuplicationsDecorator; -import java.util.Arrays; -import java.util.List; - @Properties({ @Property( + key = CoreProperties.CPD_ENGINE, + defaultValue = CoreProperties.CPD_ENGINE_DEFAULT_VALUE, + name = "Copy&Paste detection engine", + description = "Sonar embeds its own CPD engine since Sonar 2.11, but it's still possible to use the old PMD CPD engine (value 'pmd')." + + " Some Sonar users might want to keep on working with PMD CPD engine for instance to prevent any impact on measures during an upgrade of Sonar." + + " Moreover this Sonar CPD engine is not supported by all Sonar language plugins and when this support is not available," + + " the PMD CPD engine is automatically selected.", + project = true, + module = true, + global = true, + category = CoreProperties.CATEGORY_DUPLICATIONS), + @Property( + key = CoreProperties.CPD_CROSS_RPOJECT, + defaultValue = CoreProperties.CPD_CROSS_RPOJECT_DEFAULT_VALUE + "", + name = "Cross project duplicaton detection", + description = "Sonar supports the detection of cross project duplications." + + " Activating this property will slightly increase each Sonar analysis time." + + " This mode can't be used along with the PMD CPD engine.", + project = true, + module = true, + global = true, + category = CoreProperties.CATEGORY_DUPLICATIONS), + @Property( key = CoreProperties.CPD_MINIMUM_TOKENS_PROPERTY, defaultValue = CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE + "", name = "Minimum tokens", - description = "The number of duplicate tokens above which a block is considered as a duplication.", + description = "Deprecated property used only by the PMD CPD engine." + + " The number of duplicate tokens above which a block is considered as a duplication.", project = true, module = true, - global = true), + global = true, + category = CoreProperties.CATEGORY_DUPLICATIONS), @Property( key = CoreProperties.CPD_IGNORE_LITERALS_PROPERTY, defaultValue = CoreProperties.CPD_IGNORE_LITERALS_DEFAULT_VALUE + "", name = "Ignore literals", - description = "if true, CPD ignores literal value differences when evaluating a duplicate block. " + - "This means that foo=\"first string\"; and foo=\"second string\"; will be seen as equivalent.", + description = "Deprecated property used only by the PMD CPD engine." + + " If true, PMD-CPD ignores literal value differences when evaluating a duplicate block." + + " This means that foo=\"first string\"; and foo=\"second string\"; will be seen as equivalent.", project = true, module = true, - global = true), + global = true, + category = CoreProperties.CATEGORY_DUPLICATIONS), @Property( key = CoreProperties.CPD_IGNORE_IDENTIFIERS_PROPERTY, defaultValue = CoreProperties.CPD_IGNORE_IDENTIFIERS_DEFAULT_VALUE + "", name = "Ignore identifiers", - description = "Similar to 'Ignore literals' but for identifiers; i.e., variable names, methods names, and so forth.", + description = "Deprecated property used only by the PMD CPD engine." + + " Similar to 'Ignore literals' but for identifiers; i.e., variable names, methods names, and so forth.", project = true, module = true, - global = true), - @Property( - key = CoreProperties.CPD_SKIP_PROPERTY, - defaultValue = "false", - name = "Skip detection of duplicated code", - description = "Searching for duplicated code is memory hungry therefore for very big projects it can be necessary to turn the functionality off.", - project = true, - module = true, - global = true) + global = true, + category = CoreProperties.CATEGORY_DUPLICATIONS) }) public class CpdPlugin extends SonarPlugin { public List getExtensions() { - return Arrays.asList(CpdSensor.class, SumDuplicationsDecorator.class, DuplicationDensityDecorator.class, JavaCpdMapping.class); + return Arrays.asList(CpdSensor.class, SumDuplicationsDecorator.class, DuplicationDensityDecorator.class, JavaCpdMapping.class, SonarEngine.class, PmdEngine.class); } -}
\ No newline at end of file + +} diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java index 3e65dd742ea..afc0ec21b71 100644 --- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java @@ -19,101 +19,67 @@ */ package org.sonar.plugins.cpd; -import net.sourceforge.pmd.cpd.AbstractLanguage; -import net.sourceforge.pmd.cpd.TokenEntry; import org.apache.commons.configuration.Configuration; +import org.apache.commons.lang.StringUtils; import org.slf4j.LoggerFactory; import org.sonar.api.CoreProperties; -import org.sonar.api.batch.CpdMapping; import org.sonar.api.batch.Sensor; import org.sonar.api.batch.SensorContext; -import org.sonar.api.resources.Language; import org.sonar.api.resources.Project; -import org.sonar.duplications.cpd.CPD; - -import java.io.IOException; -import java.nio.charset.Charset; +import org.sonar.api.utils.Logs; public class CpdSensor implements Sensor { - private CpdMapping[] mappings; + private CpdEngine sonarEngine; + private CpdEngine pmdEngine; - public CpdSensor(CpdMapping[] mappings) { - this.mappings = mappings; + public CpdSensor(SonarEngine sonarEngine, PmdEngine pmdEngine) { + this.sonarEngine = sonarEngine; + this.pmdEngine = pmdEngine; } public boolean shouldExecuteOnProject(Project project) { - CpdMapping mapping = getMapping(project.getLanguage()); - if (mapping == null) { - LoggerFactory.getLogger(getClass()).info("Detection of duplication code is not supported for {}.", project.getLanguage()); + if (isSkipped(project)) { + LoggerFactory.getLogger(getClass()).info("Detection of duplicated code is skipped"); return false; } - if (isSkipped(project)) { - LoggerFactory.getLogger(getClass()).info("Detection of duplicated code is skipped"); + if (!getEngine(project).isLanguageSupported(project.getLanguage())) { + LoggerFactory.getLogger(getClass()).info("Detection of duplication code is not supported for {}.", project.getLanguage()); return false; } return true; } - boolean isSkipped(Project project) { - Configuration conf = project.getConfiguration(); - return conf.getBoolean("sonar.cpd." + project.getLanguageKey() + ".skip", - conf.getBoolean("sonar.cpd.skip", false)); - } - - public void analyse(Project project, SensorContext context) { - CpdMapping mapping = getMapping(project.getLanguage()); - CPD cpd = executeCPD(project, mapping, project.getFileSystem().getSourceCharset()); - saveResults(cpd, mapping, project, context); - } - - private CpdMapping getMapping(Language language) { - for (CpdMapping cpdMapping : mappings) { - if (cpdMapping.getLanguage().equals(language)) { - return cpdMapping; + private CpdEngine getEngine(Project project) { + if (isSonarEngineEnabled(project)) { + if (sonarEngine.isLanguageSupported(project.getLanguage())) { + return sonarEngine; + } else { + // fallback to PMD + return pmdEngine; } + } else { + return pmdEngine; } - return null; - } - - private void saveResults(CPD cpd, CpdMapping mapping, Project project, SensorContext context) { - CpdAnalyser cpdAnalyser = new CpdAnalyser(project, context, mapping); - cpdAnalyser.analyse(cpd.getMatches()); } - private CPD executeCPD(Project project, CpdMapping mapping, Charset encoding) { - try { - CPD cpd = configureCPD(project, mapping, encoding); - cpd.go(); - return cpd; - - } catch (Exception e) { - throw new CpdException(e); - } + boolean isSonarEngineEnabled(Project project) { + Configuration conf = project.getConfiguration(); + return StringUtils.equalsIgnoreCase(conf.getString(CoreProperties.CPD_ENGINE, CoreProperties.CPD_ENGINE_DEFAULT_VALUE), "sonar"); } - private CPD configureCPD(Project project, CpdMapping mapping, Charset encoding) throws IOException { - // To avoid a cpd bug generating error as "java.lang.IndexOutOfBoundsException: Index: 259, Size: 248" - // See http://sourceforge.net/tracker/?func=detail&atid=479921&aid=1947823&group_id=56262 for more details - TokenEntry.clearImages(); - - int minTokens = getMinimumTokens(project); - AbstractLanguage cpdLanguage = new AbstractLanguage(mapping.getTokenizer()) { - }; - - CPD cpd = new CPD(minTokens, cpdLanguage); - cpd.setEncoding(encoding.name()); - cpd.setLoadSourceCodeSlices(false); - cpd.add(project.getFileSystem().getSourceFiles(project.getLanguage())); - return cpd; + boolean isSkipped(Project project) { + Configuration conf = project.getConfiguration(); + return conf.getBoolean("sonar.cpd." + project.getLanguageKey() + ".skip", + conf.getBoolean(CoreProperties.CPD_SKIP_PROPERTY, false)); } - int getMinimumTokens(Project project) { - Configuration conf = project.getConfiguration(); - return conf.getInt("sonar.cpd." + project.getLanguageKey() + ".minimumTokens", - conf.getInt("sonar.cpd.minimumTokens", CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE)); + public void analyse(Project project, SensorContext context) { + CpdEngine engine = getEngine(project); + Logs.INFO.info("{} is used", engine); + engine.analyse(project, context); } @Override diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/DuplicationsData.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/DuplicationsData.java new file mode 100644 index 00000000000..55072da43b4 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/DuplicationsData.java @@ -0,0 +1,112 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * 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.cpd; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.sonar.api.batch.SensorContext; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.resources.Resource; + +public class DuplicationsData { + + private Resource resource; + private Set<Integer> duplicatedLines = new HashSet<Integer>(); + private double duplicatedBlocks; + private List<XmlEntry> duplicationXMLEntries = new ArrayList<XmlEntry>(); + + private SensorContext context; + + public DuplicationsData(Resource resource, SensorContext context) { + this.resource = resource; + this.context = context; + } + + public void cumulate(String targetResource, int targetDuplicationStartLine, int duplicationStartLine, int duplicatedLines) { + duplicationXMLEntries.add(new XmlEntry(targetResource, targetDuplicationStartLine, duplicationStartLine, duplicatedLines)); + for (int duplicatedLine = duplicationStartLine; duplicatedLine < duplicationStartLine + duplicatedLines; duplicatedLine++) { + this.duplicatedLines.add(duplicatedLine); + } + } + + public void cumulate(Resource targetResource, int targetDuplicationStartLine, int duplicationStartLine, int duplicatedLines) { + cumulate(context.saveResource(targetResource), targetDuplicationStartLine, duplicationStartLine, duplicatedLines); + } + + public void incrementDuplicatedBlock() { + duplicatedBlocks++; + } + + public void save() { + context.saveMeasure(resource, CoreMetrics.DUPLICATED_FILES, 1d); + context.saveMeasure(resource, CoreMetrics.DUPLICATED_LINES, (double) duplicatedLines.size()); + context.saveMeasure(resource, CoreMetrics.DUPLICATED_BLOCKS, duplicatedBlocks); + context.saveMeasure(resource, new Measure(CoreMetrics.DUPLICATIONS_DATA, getDuplicationXMLData())); + } + + private String getDuplicationXMLData() { + Collections.sort(duplicationXMLEntries, COMPARATOR); + StringBuilder duplicationXML = new StringBuilder("<duplications>"); + for (XmlEntry xmlEntry : duplicationXMLEntries) { + duplicationXML.append(xmlEntry.toString()); + } + duplicationXML.append("</duplications>"); + return duplicationXML.toString(); + } + + private static final Comparator<XmlEntry> COMPARATOR = new Comparator<XmlEntry>() { + public int compare(XmlEntry o1, XmlEntry o2) { + if (o1.startLine == o2.startLine) { + return o1.lines - o2.lines; + } + return o1.startLine - o2.startLine; + } + }; + + private static final class XmlEntry { + private String target; + private int targetStartLine; + private int startLine; + private int lines; + + private XmlEntry(String target, int targetStartLine, int startLine, int lines) { + this.target = target; + this.targetStartLine = targetStartLine; + this.startLine = startLine; + this.lines = lines; + } + + @Override + public String toString() { + return new StringBuilder().append("<duplication lines=\"").append(lines) + .append("\" start=\"").append(startLine) + .append("\" target-start=\"").append(targetStartLine) + .append("\" target-resource=\"").append(target).append("\"/>") + .toString(); + } + } + +} diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/PmdEngine.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/PmdEngine.java new file mode 100644 index 00000000000..48584a83d65 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/PmdEngine.java @@ -0,0 +1,103 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * 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.cpd; + +import java.io.IOException; +import java.nio.charset.Charset; + +import net.sourceforge.pmd.cpd.AbstractLanguage; +import net.sourceforge.pmd.cpd.TokenEntry; + +import org.apache.commons.configuration.Configuration; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.CpdMapping; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.resources.Language; +import org.sonar.api.resources.Project; +import org.sonar.duplications.cpd.CPD; + +public class PmdEngine extends CpdEngine { + + private CpdMapping[] mappings; + + public PmdEngine(CpdMapping[] mappings) { + this.mappings = mappings; + } + + @Override + public boolean isLanguageSupported(Language language) { + return getMapping(language) != null; + } + + private CpdMapping getMapping(Language language) { + for (CpdMapping cpdMapping : mappings) { + if (cpdMapping.getLanguage().equals(language)) { + return cpdMapping; + } + } + return null; + } + + @Override + public void analyse(Project project, SensorContext context) { + CpdMapping mapping = getMapping(project.getLanguage()); + CPD cpd = executeCPD(project, mapping, project.getFileSystem().getSourceCharset()); + saveResults(cpd, mapping, project, context); + } + + private void saveResults(CPD cpd, CpdMapping mapping, Project project, SensorContext context) { + CpdAnalyser cpdAnalyser = new CpdAnalyser(project, context, mapping); + cpdAnalyser.analyse(cpd.getMatches()); + } + + private CPD executeCPD(Project project, CpdMapping mapping, Charset encoding) { + try { + CPD cpd = configureCPD(project, mapping, encoding); + cpd.go(); + return cpd; + + } catch (Exception e) { + throw new CpdException(e); + } + } + + private CPD configureCPD(Project project, CpdMapping mapping, Charset encoding) throws IOException { + // To avoid a cpd bug generating error as "java.lang.IndexOutOfBoundsException: Index: 259, Size: 248" + // See http://sourceforge.net/tracker/?func=detail&atid=479921&aid=1947823&group_id=56262 for more details + TokenEntry.clearImages(); + + int minTokens = getMinimumTokens(project); + AbstractLanguage cpdLanguage = new AbstractLanguage(mapping.getTokenizer()) { + }; + + CPD cpd = new CPD(minTokens, cpdLanguage); + cpd.setEncoding(encoding.name()); + cpd.setLoadSourceCodeSlices(false); + cpd.add(project.getFileSystem().getSourceFiles(project.getLanguage())); + return cpd; + } + + int getMinimumTokens(Project project) { + Configuration conf = project.getConfiguration(); + return conf.getInt("sonar.cpd." + project.getLanguageKey() + ".minimumTokens", + conf.getInt("sonar.cpd.minimumTokens", CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE)); + } + +} diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarEngine.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarEngine.java new file mode 100644 index 00000000000..cf3e7cd08b7 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarEngine.java @@ -0,0 +1,213 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * 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.cpd; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.*; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.database.DatabaseSession; +import org.sonar.api.database.model.ResourceModel; +import org.sonar.api.resources.*; +import org.sonar.api.utils.Logs; +import org.sonar.api.utils.SonarException; +import org.sonar.batch.index.ResourcePersister; +import org.sonar.duplications.block.Block; +import org.sonar.duplications.block.BlockChunker; +import org.sonar.duplications.detector.original.OriginalCloneDetectionAlgorithm; +import org.sonar.duplications.index.CloneGroup; +import org.sonar.duplications.index.CloneIndex; +import org.sonar.duplications.index.ClonePart; +import org.sonar.duplications.java.JavaStatementBuilder; +import org.sonar.duplications.java.JavaTokenProducer; +import org.sonar.duplications.statement.Statement; +import org.sonar.duplications.statement.StatementChunker; +import org.sonar.duplications.token.TokenChunker; +import org.sonar.plugins.cpd.index.DbDuplicationsIndex; +import org.sonar.plugins.cpd.index.SonarDuplicationsIndex; + +public class SonarEngine extends CpdEngine { + + private static final int BLOCK_SIZE = 10; + + /** + * Limit of time to analyse one file (in seconds). + */ + private static final int TIMEOUT = 5 * 60; + + private final ResourcePersister resourcePersister; + private final DatabaseSession dbSession; + + /** + * For dry run, where is no access to database. + */ + public SonarEngine() { + this(null, null); + } + + public SonarEngine(ResourcePersister resourcePersister, DatabaseSession dbSession) { + this.resourcePersister = resourcePersister; + this.dbSession = dbSession; + } + + @Override + public boolean isLanguageSupported(Language language) { + return Java.INSTANCE.equals(language); + } + + /** + * @return true, if was enabled by user and database is available + */ + private boolean isCrossProject(Project project) { + return project.getConfiguration().getBoolean(CoreProperties.CPD_CROSS_RPOJECT, CoreProperties.CPD_CROSS_RPOJECT_DEFAULT_VALUE) + && resourcePersister != null && dbSession != null + && StringUtils.isBlank(project.getConfiguration().getString(CoreProperties.PROJECT_BRANCH_PROPERTY)); + } + + private static String getFullKey(Project project, Resource resource) { + return new StringBuilder(ResourceModel.KEY_SIZE) + .append(project.getKey()) + .append(':') + .append(resource.getKey()) + .toString(); + } + + @Override + public void analyse(Project project, SensorContext context) { + List<InputFile> inputFiles = project.getFileSystem().mainFiles(project.getLanguageKey()); + if (inputFiles.isEmpty()) { + return; + } + + // Create index + final SonarDuplicationsIndex index; + if (isCrossProject(project)) { + Logs.INFO.info("Cross-project analysis enabled"); + index = new SonarDuplicationsIndex(new DbDuplicationsIndex(dbSession, resourcePersister, project)); + } else { + Logs.INFO.info("Cross-project analysis disabled"); + index = new SonarDuplicationsIndex(); + } + + TokenChunker tokenChunker = JavaTokenProducer.build(); + StatementChunker statementChunker = JavaStatementBuilder.build(); + BlockChunker blockChunker = new BlockChunker(BLOCK_SIZE); + + for (InputFile inputFile : inputFiles) { + Resource resource = getResource(inputFile); + String resourceKey = getFullKey(project, resource); + + List<Statement> statements; + + Reader reader = null; + try { + reader = new InputStreamReader(new FileInputStream(inputFile.getFile()), project.getFileSystem().getSourceCharset()); + statements = statementChunker.chunk(tokenChunker.chunk(reader)); + } catch (FileNotFoundException e) { + throw new SonarException(e); + } finally { + IOUtils.closeQuietly(reader); + } + + List<Block> blocks = blockChunker.chunk(resourceKey, statements); + index.insert(resource, blocks); + } + + // Detect + ExecutorService executorService = Executors.newSingleThreadExecutor(); + try { + for (InputFile inputFile : inputFiles) { + Logs.INFO.debug("Detection of duplications for {}", inputFile.getFile()); + Resource resource = getResource(inputFile); + String resourceKey = getFullKey(project, resource); + + Collection<Block> fileBlocks = index.getByResource(resource, resourceKey); + + List<CloneGroup> clones; + try { + clones = executorService.submit(new Task(index, fileBlocks)).get(TIMEOUT, TimeUnit.SECONDS); + } catch (TimeoutException e) { + clones = null; + Logs.INFO.warn("Timeout during detection of duplications for " + inputFile.getFile(), e); + } catch (InterruptedException e) { + throw new SonarException(e); + } catch (ExecutionException e) { + throw new SonarException(e); + } + + if (clones != null && !clones.isEmpty()) { + // Save + DuplicationsData data = new DuplicationsData(resource, context); + for (CloneGroup clone : clones) { + poplulateData(data, clone); + } + data.save(); + } + } + } finally { + executorService.shutdown(); + } + } + + private static class Task implements Callable<List<CloneGroup>> { + private final CloneIndex index; + private final Collection<Block> fileBlocks; + + public Task(CloneIndex index, Collection<Block> fileBlocks) { + this.index = index; + this.fileBlocks = fileBlocks; + } + + public List<CloneGroup> call() { + return OriginalCloneDetectionAlgorithm.detect(index, fileBlocks); + } + } + + private Resource getResource(InputFile inputFile) { + return JavaFile.fromRelativePath(inputFile.getRelativePath(), false); + } + + private void poplulateData(DuplicationsData data, CloneGroup clone) { + ClonePart origin = clone.getOriginPart(); + int originLines = origin.getLineEnd() - origin.getLineStart() + 1; + + data.incrementDuplicatedBlock(); + for (ClonePart part : clone.getCloneParts()) { + if (part.equals(origin)) { + continue; + } + data.cumulate(part.getResourceId(), part.getLineStart(), origin.getLineStart(), originLines); + + if (part.getResourceId().equals(origin.getResourceId())) { + data.incrementDuplicatedBlock(); + data.cumulate(origin.getResourceId(), origin.getLineStart(), part.getLineStart(), originLines); + } + } + } + +} diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/DbDuplicationsIndex.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/DbDuplicationsIndex.java new file mode 100644 index 00000000000..c63271e4807 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/DbDuplicationsIndex.java @@ -0,0 +1,144 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * 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.cpd.index; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.persistence.Query; + +import org.hibernate.ejb.HibernateQuery; +import org.hibernate.transform.Transformers; +import org.sonar.api.database.DatabaseSession; +import org.sonar.api.database.model.Snapshot; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.batch.index.ResourcePersister; +import org.sonar.duplications.block.Block; +import org.sonar.duplications.block.ByteArray; +import org.sonar.jpa.entity.DuplicationBlock; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +public class DbDuplicationsIndex { + + private final Map<ByteArray, Collection<Block>> cache = Maps.newHashMap(); + + private final DatabaseSession session; + private final ResourcePersister resourcePersister; + private final int currentProjectSnapshotId; + private final Integer lastSnapshotId; + + public DbDuplicationsIndex(DatabaseSession session, ResourcePersister resourcePersister, Project currentProject) { + this.session = session; + this.resourcePersister = resourcePersister; + Snapshot currentSnapshot = resourcePersister.getSnapshotOrFail(currentProject); + Snapshot lastSnapshot = resourcePersister.getLastSnapshot(currentSnapshot, false); + this.currentProjectSnapshotId = currentSnapshot.getId(); + this.lastSnapshotId = lastSnapshot == null ? null : lastSnapshot.getId(); + } + + /** + * For tests. + */ + DbDuplicationsIndex(DatabaseSession session, ResourcePersister resourcePersister, Integer currentProjectSnapshotId, Integer prevSnapshotId) { + this.session = session; + this.resourcePersister = resourcePersister; + this.currentProjectSnapshotId = currentProjectSnapshotId; + this.lastSnapshotId = prevSnapshotId; + } + + int getSnapshotIdFor(Resource resource) { + return resourcePersister.getSnapshotOrFail(resource).getId(); + } + + public void prepareCache(Resource resource) { + int resourceSnapshotId = getSnapshotIdFor(resource); + + // Order of columns is important - see code below! + String sql = "SELECT DISTINCT to_blocks.hash, res.kee, to_blocks.index_in_file, to_blocks.start_line, to_blocks.end_line" + + " FROM duplications_index to_blocks, duplications_index from_blocks, snapshots snapshot, projects res" + + " WHERE from_blocks.snapshot_id = :resource_snapshot_id" + + " AND to_blocks.hash = from_blocks.hash" + + " AND to_blocks.snapshot_id = snapshot.id" + + " AND snapshot.islast = :is_last" + + " AND snapshot.project_id = res.id"; + if (lastSnapshotId != null) { + // Filter for blocks from previous snapshot of current project + sql += " AND to_blocks.project_snapshot_id != :last_project_snapshot_id"; + } + Query query = session.getEntityManager().createNativeQuery(sql) + .setParameter("resource_snapshot_id", resourceSnapshotId) + .setParameter("is_last", Boolean.TRUE); + if (lastSnapshotId != null) { + query.setParameter("last_project_snapshot_id", lastSnapshotId); + } + // Ugly hack for mapping results of custom SQL query into plain list (MyBatis is coming soon) + ((HibernateQuery) query).getHibernateQuery().setResultTransformer(Transformers.TO_LIST); + List<List<Object>> blocks = query.getResultList(); + + cache.clear(); + for (List<Object> dbBlock : blocks) { + String hash = (String) dbBlock.get(0); + String resourceKey = (String) dbBlock.get(1); + int indexInFile = ((Number) dbBlock.get(2)).intValue(); + int startLine = ((Number) dbBlock.get(3)).intValue(); + int endLine = ((Number) dbBlock.get(4)).intValue(); + + Block block = new Block(resourceKey, new ByteArray(hash), indexInFile, startLine, endLine); + + // Group blocks by hash + Collection<Block> sameHash = cache.get(block.getBlockHash()); + if (sameHash == null) { + sameHash = Lists.newArrayList(); + cache.put(block.getBlockHash(), sameHash); + } + sameHash.add(block); + } + } + + public Collection<Block> getByHash(ByteArray hash) { + Collection<Block> result = cache.get(hash); + if (result != null) { + return result; + } else { + return Collections.emptyList(); + } + } + + public void insert(Resource resource, Collection<Block> blocks) { + int resourceSnapshotId = getSnapshotIdFor(resource); + for (Block block : blocks) { + DuplicationBlock dbBlock = new DuplicationBlock( + currentProjectSnapshotId, + resourceSnapshotId, + block.getBlockHash().toString(), + block.getIndexInFile(), + block.getFirstLineNumber(), + block.getLastLineNumber()); + session.save(dbBlock); + } + session.commit(); + } + +} diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/SonarDuplicationsIndex.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/SonarDuplicationsIndex.java new file mode 100644 index 00000000000..f5ddf5bd579 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/SonarDuplicationsIndex.java @@ -0,0 +1,81 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * 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.cpd.index; + +import java.util.Collection; +import java.util.List; + +import org.sonar.api.resources.Resource; +import org.sonar.duplications.block.Block; +import org.sonar.duplications.block.ByteArray; +import org.sonar.duplications.index.AbstractCloneIndex; +import org.sonar.duplications.index.CloneIndex; +import org.sonar.duplications.index.PackedMemoryCloneIndex; + +import com.google.common.collect.Lists; + +public class SonarDuplicationsIndex extends AbstractCloneIndex { + + private final CloneIndex mem = new PackedMemoryCloneIndex(); + private final DbDuplicationsIndex db; + + public SonarDuplicationsIndex() { + this(null); + } + + public SonarDuplicationsIndex(DbDuplicationsIndex db) { + this.db = db; + } + + public void insert(Resource resource, Collection<Block> blocks) { + for (Block block : blocks) { + mem.insert(block); + } + if (db != null) { + db.insert(resource, blocks); + } + } + + public Collection<Block> getByResource(Resource resource, String resourceKey) { + if (db != null) { + db.prepareCache(resource); + } + return mem.getByResourceId(resourceKey); + } + + public Collection<Block> getBySequenceHash(ByteArray hash) { + if (db == null) { + return mem.getBySequenceHash(hash); + } else { + List<Block> result = Lists.newArrayList(mem.getBySequenceHash(hash)); + result.addAll(db.getByHash(hash)); + return result; + } + } + + public Collection<Block> getByResourceId(String resourceId) { + throw new UnsupportedOperationException(); + } + + public void insert(Block block) { + throw new UnsupportedOperationException(); + } + +} diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdAnalyserTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdAnalyserTest.java index 827c266119a..a5d3f07bba8 100644 --- a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdAnalyserTest.java +++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdAnalyserTest.java @@ -130,8 +130,9 @@ public class CpdAnalyserTest { verify(context).saveMeasure( eq(resource1), argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>" + + "<duplication lines=\"100\" start=\"5\" target-start=\"15\" target-resource=\"key3\"/>" + "<duplication lines=\"200\" start=\"5\" target-start=\"15\" target-resource=\"key2\"/>" - + "<duplication lines=\"100\" start=\"5\" target-start=\"15\" target-resource=\"key3\"/>" + "</duplications>"))); + + "</duplications>"))); verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_FILES, 1d); verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_LINES, 200d); diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java index a7699d21c4c..f0dbcd4bbbd 100644 --- a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java +++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java @@ -19,16 +19,16 @@ */ package org.sonar.plugins.cpd; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + import org.apache.commons.configuration.PropertiesConfiguration; import org.junit.Test; -import org.sonar.api.CoreProperties; import org.sonar.api.batch.CpdMapping; import org.sonar.api.resources.Project; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - public class CpdSensorTest { @Test @@ -38,7 +38,7 @@ public class CpdSensorTest { Project project = createJavaProject().setConfiguration(conf); - CpdSensor sensor = new CpdSensor(new CpdMapping[0]); + CpdSensor sensor = new CpdSensor(new SonarEngine(), new PmdEngine(new CpdMapping[0])); assertTrue(sensor.isSkipped(project)); } @@ -46,7 +46,7 @@ public class CpdSensorTest { public void doNotSkipByDefault() { Project project = createJavaProject().setConfiguration(new PropertiesConfiguration()); - CpdSensor sensor = new CpdSensor(new CpdMapping[0]); + CpdSensor sensor = new CpdSensor(new SonarEngine(), new PmdEngine(new CpdMapping[0])); assertFalse(sensor.isSkipped(project)); } @@ -59,41 +59,20 @@ public class CpdSensorTest { Project phpProject = createPhpProject().setConfiguration(conf); Project javaProject = createJavaProject().setConfiguration(conf); - CpdSensor sensor = new CpdSensor(new CpdMapping[0]); + CpdSensor sensor = new CpdSensor(new SonarEngine(), new PmdEngine(new CpdMapping[0])); assertTrue(sensor.isSkipped(phpProject)); assertFalse(sensor.isSkipped(javaProject)); } @Test - public void defaultMinimumTokens() { - Project project = createJavaProject().setConfiguration(new PropertiesConfiguration()); - - CpdSensor sensor = new CpdSensor(new CpdMapping[0]); - assertEquals(CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE, sensor.getMinimumTokens(project)); - } - - @Test - public void generalMinimumTokens() { + public void engine() { PropertiesConfiguration conf = new PropertiesConfiguration(); - conf.setProperty("sonar.cpd.minimumTokens", "33"); Project project = createJavaProject().setConfiguration(conf); + CpdSensor sensor = new CpdSensor(new SonarEngine(), new PmdEngine(new CpdMapping[0])); - CpdSensor sensor = new CpdSensor(new CpdMapping[0]); - assertEquals(33, sensor.getMinimumTokens(project)); - } - - @Test - public void minimumTokensByLanguage() { - PropertiesConfiguration conf = new PropertiesConfiguration(); - conf.setProperty("sonar.cpd.minimumTokens", "100"); - conf.setProperty("sonar.cpd.php.minimumTokens", "33"); - - Project phpProject = createPhpProject().setConfiguration(conf); - Project javaProject = createJavaProject().setConfiguration(conf); - - CpdSensor sensor = new CpdSensor(new CpdMapping[0]); - assertEquals(100, sensor.getMinimumTokens(javaProject)); - assertEquals(33, sensor.getMinimumTokens(phpProject)); + assertThat(sensor.isSonarEngineEnabled(project), is(true)); + conf.setProperty("sonar.cpd.engine", "pmd"); + assertThat(sensor.isSonarEngineEnabled(project), is(false)); } private Project createJavaProject() { @@ -103,4 +82,5 @@ public class CpdSensorTest { private Project createPhpProject() { return new Project("php_project").setLanguageKey("php"); } + } diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/PmdEngineTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/PmdEngineTest.java new file mode 100644 index 00000000000..11201041de9 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/PmdEngineTest.java @@ -0,0 +1,72 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * 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.cpd; + +import static junit.framework.Assert.assertEquals; + +import org.apache.commons.configuration.PropertiesConfiguration; +import org.junit.Test; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.CpdMapping; +import org.sonar.api.resources.Project; + +public class PmdEngineTest { + + @Test + public void defaultMinimumTokens() { + Project project = createJavaProject().setConfiguration(new PropertiesConfiguration()); + + PmdEngine sensor = new PmdEngine(new CpdMapping[0]); + assertEquals(CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE, sensor.getMinimumTokens(project)); + } + + @Test + public void generalMinimumTokens() { + PropertiesConfiguration conf = new PropertiesConfiguration(); + conf.setProperty("sonar.cpd.minimumTokens", "33"); + Project project = createJavaProject().setConfiguration(conf); + + PmdEngine sensor = new PmdEngine(new CpdMapping[0]); + assertEquals(33, sensor.getMinimumTokens(project)); + } + + @Test + public void minimumTokensByLanguage() { + PropertiesConfiguration conf = new PropertiesConfiguration(); + conf.setProperty("sonar.cpd.minimumTokens", "100"); + conf.setProperty("sonar.cpd.php.minimumTokens", "33"); + + Project phpProject = createPhpProject().setConfiguration(conf); + Project javaProject = createJavaProject().setConfiguration(conf); + + PmdEngine sensor = new PmdEngine(new CpdMapping[0]); + assertEquals(100, sensor.getMinimumTokens(javaProject)); + assertEquals(33, sensor.getMinimumTokens(phpProject)); + } + + private Project createJavaProject() { + return new Project("java_project").setLanguageKey("java"); + } + + private Project createPhpProject() { + return new Project("php_project").setLanguageKey("php"); + } + +} diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/DbDuplicationsIndexTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/DbDuplicationsIndexTest.java new file mode 100644 index 00000000000..fedf032033c --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/DbDuplicationsIndexTest.java @@ -0,0 +1,75 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * 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.cpd.index; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; + +import org.junit.Test; +import org.sonar.api.resources.JavaFile; +import org.sonar.api.resources.Resource; +import org.sonar.duplications.block.Block; +import org.sonar.duplications.block.ByteArray; +import org.sonar.jpa.test.AbstractDbUnitTestCase; + +public class DbDuplicationsIndexTest extends AbstractDbUnitTestCase { + + private DbDuplicationsIndex index; + + @Test + public void shouldGetByHash() { + Resource resource = new JavaFile("foo"); + index = spy(new DbDuplicationsIndex(getSession(), null, 9, 7)); + doReturn(10).when(index).getSnapshotIdFor(resource); + setupData("shouldGetByHash"); + + index.prepareCache(resource); + Collection<Block> blocks = index.getByHash(new ByteArray("aa")); + Iterator<Block> blocksIterator = blocks.iterator(); + + assertThat(blocks.size(), is(1)); + + Block block = blocksIterator.next(); + assertThat("block resourceId", block.getResourceId(), is("bar-last")); + assertThat("block hash", block.getBlockHash(), is(new ByteArray("aa"))); + assertThat("block index in file", block.getIndexInFile(), is(0)); + assertThat("block start line", block.getFirstLineNumber(), is(1)); + assertThat("block end line", block.getLastLineNumber(), is(2)); + } + + @Test + public void shouldInsert() { + Resource resource = new JavaFile("foo"); + index = spy(new DbDuplicationsIndex(getSession(), null, 1, null)); + doReturn(2).when(index).getSnapshotIdFor(resource); + setupData("shouldInsert"); + + index.insert(resource, Arrays.asList(new Block("foo", new ByteArray("bb"), 0, 1, 2))); + + checkTables("shouldInsert", "duplications_index"); + } + +} diff --git a/plugins/sonar-cpd-plugin/src/test/resources/org/sonar/plugins/cpd/index/DbDuplicationsIndexTest/shouldGetByHash.xml b/plugins/sonar-cpd-plugin/src/test/resources/org/sonar/plugins/cpd/index/DbDuplicationsIndexTest/shouldGetByHash.xml new file mode 100644 index 00000000000..9581b0be96c --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/test/resources/org/sonar/plugins/cpd/index/DbDuplicationsIndexTest/shouldGetByHash.xml @@ -0,0 +1,47 @@ +<dataset> + + <snapshots id="1" status="P" islast="false" project_id="0" /> + <snapshots id="2" status="P" islast="false" project_id="1" /> + <projects id="1" kee="bar-old" enabled="true" scope="FIL" qualifier="CLA" /> + + <snapshots id="3" status="P" islast="true" /> + <snapshots id="4" status="P" islast="true" project_id="2" /> + <projects id="2" kee="bar-last" enabled="true" scope="FIL" qualifier="CLA" /> + + <snapshots id="5" status="P" islast="false" /> + <snapshots id="6" status="P" islast="false" project_id="3" /> + <projects id="3" kee="foo-old" enabled="true" scope="FIL" qualifier="CLA" /> + + <snapshots id="7" status="P" islast="true" /> + <snapshots id="8" status="P" islast="true" project_id="4" /> + <projects id="4" kee="foo-last" enabled="true" scope="FIL" qualifier="CLA" /> + + <snapshots id="9" status="U" islast="false" /> + <snapshots id="10" status="U" islast="false" project_id="5" /> + <projects id="5" kee="foo" enabled="true" scope="FIL" qualifier="CLA" /> + + <!-- Old snapshot of another project --> + <!-- bar-old --> + <duplications_index id="1" project_snapshot_id="1" snapshot_id="2" hash="bb" index_in_file="0" start_line="0" end_line="0" /> + + <!-- Last snapshot of another project --> + <!-- bar-last --> + <duplications_index id="2" project_snapshot_id="3" snapshot_id="4" hash="aa" index_in_file="0" start_line="1" end_line="2" /> + + <!-- Old snapshot of current project --> + <!-- foo-old --> + <duplications_index id="3" project_snapshot_id="5" snapshot_id="6" hash="bb" index_in_file="0" start_line="0" end_line="0" /> + + <!-- Last snapshot of current project --> + <!-- foo-last --> + <duplications_index id="4" project_snapshot_id="7" snapshot_id="8" hash="bb" index_in_file="0" start_line="0" end_line="0" /> + + <!-- New snapshot of current project --> + <!-- foo --> + <duplications_index id="5" project_snapshot_id="9" snapshot_id="10" hash="aa" index_in_file="0" start_line="0" end_line="0" /> + + <!-- Note that there is two blocks with same hash for current analysis to verify that we use "SELECT DISTINCT", --> + <!-- without "DISTINCT" we will select block from "bar-last" two times. --> + <duplications_index id="6" project_snapshot_id="9" snapshot_id="10" hash="aa" index_in_file="1" start_line="1" end_line="1" /> + +</dataset> diff --git a/plugins/sonar-cpd-plugin/src/test/resources/org/sonar/plugins/cpd/index/DbDuplicationsIndexTest/shouldInsert-result.xml b/plugins/sonar-cpd-plugin/src/test/resources/org/sonar/plugins/cpd/index/DbDuplicationsIndexTest/shouldInsert-result.xml new file mode 100644 index 00000000000..5848ecb5723 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/test/resources/org/sonar/plugins/cpd/index/DbDuplicationsIndexTest/shouldInsert-result.xml @@ -0,0 +1,9 @@ +<dataset> + + <snapshots id="1" status="U" islast="false" project_id="0" /> + <snapshots id="2" status="U" islast="false" project_id="1" /> + <projects id="1" kee="foo" enabled="true" scope="FIL" qualifier="CLA" /> + + <duplications_index id="1" project_snapshot_id="1" snapshot_id="2" hash="bb" index_in_file="0" start_line="1" end_line="2" /> + +</dataset> diff --git a/plugins/sonar-cpd-plugin/src/test/resources/org/sonar/plugins/cpd/index/DbDuplicationsIndexTest/shouldInsert.xml b/plugins/sonar-cpd-plugin/src/test/resources/org/sonar/plugins/cpd/index/DbDuplicationsIndexTest/shouldInsert.xml new file mode 100644 index 00000000000..940281a0599 --- /dev/null +++ b/plugins/sonar-cpd-plugin/src/test/resources/org/sonar/plugins/cpd/index/DbDuplicationsIndexTest/shouldInsert.xml @@ -0,0 +1,7 @@ +<dataset> + + <snapshots id="1" status="U" islast="false" project_id="0" /> + <snapshots id="2" status="U" islast="false" project_id="1" /> + <projects id="1" kee="foo" enabled="true" scope="FIL" qualifier="CLA" /> + +</dataset> diff --git a/plugins/sonar-dbcleaner-plugin/pom.xml b/plugins/sonar-dbcleaner-plugin/pom.xml index 20f617faefe..2a937dee1b4 100644 --- a/plugins/sonar-dbcleaner-plugin/pom.xml +++ b/plugins/sonar-dbcleaner-plugin/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar</artifactId> - <version>2.10</version> + <version>2.11</version> <relativePath>../..</relativePath> </parent> <groupId>org.codehaus.sonar.plugins</groupId> diff --git a/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/api/PurgeUtils.java b/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/api/PurgeUtils.java index c417befec67..c18a68dea7b 100644 --- a/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/api/PurgeUtils.java +++ b/plugins/sonar-dbcleaner-plugin/src/main/java/org/sonar/plugins/dbcleaner/api/PurgeUtils.java @@ -19,14 +19,20 @@ */ package org.sonar.plugins.dbcleaner.api; +import java.util.List; + +import javax.persistence.Query; + import org.apache.commons.configuration.Configuration; import org.sonar.api.database.DatabaseSession; -import org.sonar.api.database.model.*; +import org.sonar.api.database.model.MeasureData; +import org.sonar.api.database.model.MeasureModel; +import org.sonar.api.database.model.RuleFailureModel; +import org.sonar.api.database.model.Snapshot; +import org.sonar.api.database.model.SnapshotSource; import org.sonar.api.design.DependencyDto; import org.sonar.api.utils.TimeProfiler; - -import javax.persistence.Query; -import java.util.List; +import org.sonar.jpa.entity.DuplicationBlock; /** * @since 2.5 @@ -58,6 +64,7 @@ public final class PurgeUtils { deleteSources(session, snapshotIds); deleteViolations(session, snapshotIds); deleteDependencies(session, snapshotIds); + deleteDuplicationBlocks(session, snapshotIds); deleteSnapshots(session, snapshotIds); } @@ -97,6 +104,13 @@ public final class PurgeUtils { } /** + * @since 2.11 + */ + private static void deleteDuplicationBlocks(DatabaseSession session, List<Integer> snapshotIds) { + executeQuery(session, "delete duplication blocks", snapshotIds, "delete from " + DuplicationBlock.class.getSimpleName() + " e where e.snapshotId in (:ids)"); + } + + /** * Delete SNAPSHOTS table */ public static void deleteSnapshots(DatabaseSession session, List<Integer> snapshotIds) { diff --git a/plugins/sonar-dbcleaner-plugin/src/test/resources/org/sonar/plugins/dbcleaner/api/PurgeUtilsTest/purgeSnapshots-result.xml b/plugins/sonar-dbcleaner-plugin/src/test/resources/org/sonar/plugins/dbcleaner/api/PurgeUtilsTest/purgeSnapshots-result.xml index 912541667d8..f27fa810e06 100644 --- a/plugins/sonar-dbcleaner-plugin/src/test/resources/org/sonar/plugins/dbcleaner/api/PurgeUtilsTest/purgeSnapshots-result.xml +++ b/plugins/sonar-dbcleaner-plugin/src/test/resources/org/sonar/plugins/dbcleaner/api/PurgeUtilsTest/purgeSnapshots-result.xml @@ -108,4 +108,7 @@ <!--parent_dependency_id="[null]" project_snapshot_id="1"--> <!--dep_usage="INHERITS" dep_weight="1" from_scope="FIL" to_scope="FIL"/>--> -</dataset>
\ No newline at end of file + <!--<duplications_index id="1" project_snapshot_id="1" snapshot_id="3" hash="bb" index_in_file="0" start_line="0" end_line="0" />--> + <!--<duplications_index id="2" project_snapshot_id="1" snapshot_id="4" hash="bb" index_in_file="0" start_line="0" end_line="0" />--> + +</dataset> diff --git a/plugins/sonar-dbcleaner-plugin/src/test/resources/org/sonar/plugins/dbcleaner/api/PurgeUtilsTest/purgeSnapshots.xml b/plugins/sonar-dbcleaner-plugin/src/test/resources/org/sonar/plugins/dbcleaner/api/PurgeUtilsTest/purgeSnapshots.xml index 2b92c63361a..4627eaaf748 100644 --- a/plugins/sonar-dbcleaner-plugin/src/test/resources/org/sonar/plugins/dbcleaner/api/PurgeUtilsTest/purgeSnapshots.xml +++ b/plugins/sonar-dbcleaner-plugin/src/test/resources/org/sonar/plugins/dbcleaner/api/PurgeUtilsTest/purgeSnapshots.xml @@ -108,4 +108,7 @@ parent_dependency_id="[null]" project_snapshot_id="1" dep_usage="INHERITS" dep_weight="1" from_scope="FIL" to_scope="FIL" /> -</dataset>
\ No newline at end of file + <duplications_index id="1" project_snapshot_id="1" snapshot_id="3" hash="bb" index_in_file="0" start_line="0" end_line="0" /> + <duplications_index id="2" project_snapshot_id="1" snapshot_id="4" hash="bb" index_in_file="0" start_line="0" end_line="0" /> + +</dataset> diff --git a/plugins/sonar-design-plugin/pom.xml b/plugins/sonar-design-plugin/pom.xml index f9bc82316d0..3aa163be116 100644 --- a/plugins/sonar-design-plugin/pom.xml +++ b/plugins/sonar-design-plugin/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar</artifactId> - <version>2.10</version> + <version>2.11</version> <relativePath>../..</relativePath> </parent> <groupId>org.codehaus.sonar.plugins</groupId> diff --git a/plugins/sonar-design-plugin/src/main/java/org/sonar/plugins/design/DesignPlugin.java b/plugins/sonar-design-plugin/src/main/java/org/sonar/plugins/design/DesignPlugin.java index a53bdf72f70..ac392718a4f 100644 --- a/plugins/sonar-design-plugin/src/main/java/org/sonar/plugins/design/DesignPlugin.java +++ b/plugins/sonar-design-plugin/src/main/java/org/sonar/plugins/design/DesignPlugin.java @@ -19,7 +19,8 @@ */ package org.sonar.plugins.design; -import org.sonar.api.*; +import com.google.common.collect.Lists; +import org.sonar.api.SonarPlugin; import org.sonar.plugins.design.batch.*; import org.sonar.plugins.design.ui.dependencies.GwtDependenciesTab; import org.sonar.plugins.design.ui.lcom4.GwtLcom4Tab; @@ -29,21 +30,12 @@ import org.sonar.plugins.design.ui.widgets.ChidamberKemererWidget; import org.sonar.plugins.design.ui.widgets.FileDesignWidget; import org.sonar.plugins.design.ui.widgets.PackageDesignWidget; -import java.util.ArrayList; import java.util.List; -@Properties({ - @Property( - key = CoreProperties.DESIGN_SKIP_DESIGN_PROPERTY, - defaultValue = "" + CoreProperties.DESIGN_SKIP_DESIGN_DEFAULT_VALUE, - name = "Skip design analysis", - project = true, - global = true) -}) public class DesignPlugin extends SonarPlugin { - public List<Class<? extends Extension>> getExtensions() { - List<Class<? extends Extension>> extensions = new ArrayList<Class<? extends Extension>>(); + public List getExtensions() { + List extensions = Lists.newArrayList(); // Batch extensions.add(MavenDependenciesSensor.class); diff --git a/plugins/sonar-email-notifications-plugin/pom.xml b/plugins/sonar-email-notifications-plugin/pom.xml index e329229436b..879c49370e8 100644 --- a/plugins/sonar-email-notifications-plugin/pom.xml +++ b/plugins/sonar-email-notifications-plugin/pom.xml @@ -5,7 +5,7 @@ <parent> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar</artifactId> - <version>2.10</version> + <version>2.11</version> <relativePath>../..</relativePath> </parent> diff --git a/plugins/sonar-findbugs-plugin/pom.xml b/plugins/sonar-findbugs-plugin/pom.xml index 2d85751974f..e1dd9a46817 100644 --- a/plugins/sonar-findbugs-plugin/pom.xml +++ b/plugins/sonar-findbugs-plugin/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar</artifactId> - <version>2.10</version> + <version>2.11</version> <relativePath>../..</relativePath> </parent> <groupId>org.codehaus.sonar.plugins</groupId> diff --git a/plugins/sonar-googleanalytics-plugin/pom.xml b/plugins/sonar-googleanalytics-plugin/pom.xml index d48b1f1f1e7..6e0bca8fa2b 100644 --- a/plugins/sonar-googleanalytics-plugin/pom.xml +++ b/plugins/sonar-googleanalytics-plugin/pom.xml @@ -5,7 +5,7 @@ <parent> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar</artifactId> - <version>2.10</version> + <version>2.11</version> <relativePath>../..</relativePath> </parent> diff --git a/plugins/sonar-l10n-en-plugin/pom.xml b/plugins/sonar-l10n-en-plugin/pom.xml index b8726715aeb..e1231ed3e6a 100644 --- a/plugins/sonar-l10n-en-plugin/pom.xml +++ b/plugins/sonar-l10n-en-plugin/pom.xml @@ -5,7 +5,7 @@ <parent> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar</artifactId> - <version>2.10</version> + <version>2.11</version> <relativePath>../..</relativePath> </parent> diff --git a/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties index 8aa669a8ae4..39bb4e1b10a 100644 --- a/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties +++ b/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties @@ -21,6 +21,7 @@ backup_verb=Backup blocker=Blocker bold=Bold build_date=Build date +build_time=Build time cancel=Cancel category=Category calendar=Calendar @@ -278,6 +279,10 @@ manual_measures.page=Manual Measures my_profile.page=My Profile project_roles.page=Project Roles project_settings.page=Settings +project_links.page=Links +project_exclusions.page=Exclusions +project_history.page=History Deletion +project_deletion.page=Project Deletion quality_profiles.page=Quality Profiles reviews.page=Reviews settings.page=General Settings @@ -375,6 +380,7 @@ reviews.without_false_positives=Without false positives reviews.showing_false_positives_only=Showing false positives only reviews.why_false_positive=Why is it a false-positive ? reviews.why_not_false_positive=Why is it not a false-positive anymore ? +reviews.user_does_not_exist=\ : user does not exist. \\nPlease select a valid user or leave the field blank. #------------------------------------------------------------------------------ @@ -416,6 +422,23 @@ dashboard.update_dashboard=Update dashboard #------------------------------------------------------------------------------ # +# SETTINGS +# +#------------------------------------------------------------------------------ +settings.save_category=Save {0} Settings +property.category.email=Email +property.category.general=General +property.category.security=Security +property.category.java=Java +property.category.differentialViews=Differential Views +property.category.codeCoverage=Code Coverage +property.category.duplications=Duplications +property.category.localization=Localization +property.category.server_id=Server ID + + +#------------------------------------------------------------------------------ +# # WIDGETS # #------------------------------------------------------------------------------ @@ -494,6 +517,13 @@ widget.size.methods.suffix=\ methods widget.size.accessors.suffix=\ accessors widget.size.paragraphs.suffix=\ paragraphs +widget.timeline.name=Timeline +widget.timeline.description=Displays up to 3 metrics on a history chart. +widget.timeline.timeline_not_displayed=The timeline won't be displayed as currently only 1 snapshot is available. + +widget.time_machine.name=History Table +widget.time_machine.description=Displays up to 10 metrics in a table, showing their value for a specified number of past snapshots. + widget.ckjm.name=Chidamber & Kemerer widget.ckjm.description=Reports on LCOM4 and RFC average and distribution. widget.ckjm.lcom4=LCOM4 @@ -569,6 +599,25 @@ manual_measures.pending_message=Pending measures are marked with orange box. The #------------------------------------------------------------------------------ # +# PROJECT HISTORY SERVICE +# +#------------------------------------------------------------------------------ + +project_history.page_title=Delete quality snapshots from project history +project_history.col.year=Year +project_history.col.month=Month +project_history.col.time=Time +project_history.col.events=Events +project_history.col.action=Action +project_history.delete=Delete +project_history.last_snapshot=Last snapshot +project_history.delete_snapshot=Delete snapshot +project_history.snapshot_deleted=The snapshot is deleted. +project_history.are_you_sure_delete_snapshot_x=Are you sure you want to delete the snapshot created on "{0}"? + + +#------------------------------------------------------------------------------ +# # TIME MACHINE # #------------------------------------------------------------------------------ @@ -623,6 +672,7 @@ quality_profiles.profile_inheritance=Profile inheritance quality_profiles.available_projects=Available projects quality_profiles.associated_projects=Associated projects quality_profiles.no_projects_associated_to_profile_x=No projects are explicitly associated to the profile "{0}". +quality_profiles.projects_warning=List of projects explicitly associated to this Quality profile : quality_profiles.no_permalinks=No permalinks quality_profiles.including_x_overriding.suffix=, incl. {0} overriding quality_profiles.profile_cant_be_edited=This profile can not be edited. @@ -695,7 +745,7 @@ email_configuration.from_address=From address email_configuration.from_address.description=Emails will come from this address. For example - "noreply@sonarsource.com". Note that server may ignore this setting (like does GMail). email_configuration.email_prefix=Email prefix email_configuration.email_prefix.description=This prefix will be prepended to all outgoing email subjects. -email_configuration.save_settings=Save +email_configuration.save_settings=Save Email Settings email_configuration.saving_settings=Saving email_configuration.settings_saved=Settings are saved. @@ -706,13 +756,30 @@ email_configuration.test.subject=Subject email_configuration.test.subject_text=Test Message from Sonar email_configuration.test.message=Message email_configuration.test.message_text=This is a test message from Sonar -email_configuration.test.send=Send test email -email_configuration.test.sending=Sending test email +email_configuration.test.send=Send Test Email +email_configuration.test.sending=Sending Test Email email_configuration.test.email_was_sent_to_x=Email was sent to {0} #------------------------------------------------------------------------------ # +# SERVER KEY CONFIGURATION +# +#------------------------------------------------------------------------------ +server_id_configuration.page=Server ID +server_id_configuration.generate_button=Generate ID +server_id_configuration.generating_button=Generating ID... +server_id_configuration.bad_key=The ID is not valid anymore. Please check the organisation and the IP address. +server_id_configuration.information=The Server ID is a unique identifier of this Sonar instance. It is used for example to obtain a license key for the SonarSource's commercial plugins. Two fields have to be provided to generate the ID : organisation name and one of the IP addresses of the machine that hosts this server. +server_id_configuration.organisation.title=Organisation +server_id_configuration.organisation.desc=Name of the organisation +server_id_configuration.ip.title=Fixed IP Address +server_id_configuration.ip.desc=A server ID is linked to the IP address of the hosting machine that runs Sonar. If the server IP address was to changed, the server ID will have to be regenerated. The valid addresses are : +server_id_configuration.generation_error=Organisation and/or IP address are not valid. + + +#------------------------------------------------------------------------------ +# # NOTIFICATIONS # #------------------------------------------------------------------------------ diff --git a/plugins/sonar-pmd-plugin/pom.xml b/plugins/sonar-pmd-plugin/pom.xml index 485de007e62..3036e4d414f 100644 --- a/plugins/sonar-pmd-plugin/pom.xml +++ b/plugins/sonar-pmd-plugin/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar</artifactId> - <version>2.10</version> + <version>2.11</version> <relativePath>../..</relativePath> </parent> <groupId>org.codehaus.sonar.plugins</groupId> diff --git a/plugins/sonar-squid-java-plugin/pom.xml b/plugins/sonar-squid-java-plugin/pom.xml index 199b2215f19..00e7147bd46 100644 --- a/plugins/sonar-squid-java-plugin/pom.xml +++ b/plugins/sonar-squid-java-plugin/pom.xml @@ -4,7 +4,7 @@ <parent> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar</artifactId> - <version>2.10</version> + <version>2.11</version> <relativePath>../..</relativePath> </parent> <groupId>org.codehaus.sonar.plugins</groupId> diff --git a/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/java/bytecode/ClassworldsClassLoader.java b/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/java/bytecode/ClassworldsClassLoader.java index 4cc10aedce1..6a814c8499f 100644 --- a/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/java/bytecode/ClassworldsClassLoader.java +++ b/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/java/bytecode/ClassworldsClassLoader.java @@ -45,7 +45,7 @@ public final class ClassworldsClassLoader { public static ClassLoader create(Collection<File> bytecodeFilesOrDirectories) { try { ClassWorld world = new ClassWorld(); - ClassRealm realm = world.newRealm("squid.project"); + ClassRealm realm = world.newRealm("squid.project", null /* explicit declaration that parent should be bootstrap class loader */); for (File bytecode : bytecodeFilesOrDirectories) { URL url = getURL(bytecode); 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 cd7fb31c5a3..ab79465888c 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 @@ -19,6 +19,7 @@ */ package org.sonar.plugins.squid; +import org.sonar.api.CoreProperties; import org.sonar.api.Properties; import org.sonar.api.Property; import org.sonar.api.SonarPlugin; @@ -34,7 +35,9 @@ import java.util.List; name = "Separate accessors", description = "Flag whether Squid should separate accessors (getters/setters) from methods. " + "In that case, accessors are not counted in metrics such as complexity or API documentation.", - project = true, global = true), + project = true, + global = true, + category = CoreProperties.CATEGORY_JAVA), @Property(key = SquidPluginProperties.FIELDS_TO_EXCLUDE_FROM_LCOM4_COMPUTATION, defaultValue = SquidPluginProperties.FIELDS_TO_EXCLUDE_FROM_LCOM4_COMPUTATION_DEFAULT_VALUE, name = "List of fields to exclude from LCOM4 computation", @@ -42,8 +45,18 @@ import java.util.List; "unexpectedly and artificially decrease the LCOM4 measure. " + "The best example is a logger used by all methods of a class. " + "All field names to exclude from LCOM4 computation must be separated by a comma.", - project = true, global = true)}) -public class SquidPlugin extends SonarPlugin { + project = true, + global = true, + category = CoreProperties.CATEGORY_JAVA), + @Property( + key = CoreProperties.DESIGN_SKIP_DESIGN_PROPERTY, + defaultValue = "" + CoreProperties.DESIGN_SKIP_DESIGN_DEFAULT_VALUE, + name = "Skip design analysis", + project = true, + global = true, + category = CoreProperties.CATEGORY_JAVA) +}) +public final class SquidPlugin extends SonarPlugin { public List getExtensions() { return Arrays.asList(SquidSensor.class, SquidRuleRepository.class, JavaSourceImporter.class, diff --git a/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/java/bytecode/ClassworldsClassLoaderTest.java b/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/java/bytecode/ClassworldsClassLoaderTest.java index ea93533cffc..7f248c6eef3 100644 --- a/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/java/bytecode/ClassworldsClassLoaderTest.java +++ b/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/java/bytecode/ClassworldsClassLoaderTest.java @@ -19,6 +19,7 @@ */ package org.sonar.java.bytecode; +import org.codehaus.classworlds.ClassWorld; import org.junit.Test; import org.sonar.java.ast.SquidTestUtils; @@ -39,6 +40,25 @@ public class ClassworldsClassLoaderTest { assertThat(ClassworldsClassLoader.create(Collections.<File>emptyList()), not(nullValue())); } + /** + * See SONAR-2824: + * ClassLoader created by {@link ClassworldsClassLoader}, + * should be able to load classes only from JDK and from provided list of JAR-files, + * thus it shouldn't be able to load class {@link ClassWorld}. + */ + @Test + public void shouldBeIsolated() throws ClassNotFoundException { + ClassLoader classloader = ClassworldsClassLoader.create(Collections.EMPTY_LIST); + try { + classloader.loadClass(ClassWorld.class.getName()); + fail(); + } catch (ClassNotFoundException e) { + // ok + } + assertThat(classloader.loadClass("java.lang.Integer"), not(nullValue())); + assertThat(classloader.getResource("java/lang/Integer.class"), not(nullValue())); + } + @Test public void createFromDirectory() throws ClassNotFoundException { File dir = SquidTestUtils.getFile("/bytecode/bin/"); diff --git a/plugins/sonar-surefire-plugin/pom.xml b/plugins/sonar-surefire-plugin/pom.xml index 1c32201118e..6fd049c15f7 100644 --- a/plugins/sonar-surefire-plugin/pom.xml +++ b/plugins/sonar-surefire-plugin/pom.xml @@ -5,7 +5,7 @@ <parent> <groupId>org.codehaus.sonar</groupId> <artifactId>sonar</artifactId> - <version>2.10</version> + <version>2.11</version> <relativePath>../..</relativePath> </parent> diff --git a/plugins/sonar-surefire-plugin/src/main/java/org/sonar/plugins/surefire/api/AbstractSurefireParser.java b/plugins/sonar-surefire-plugin/src/main/java/org/sonar/plugins/surefire/api/AbstractSurefireParser.java index 85e7a2682f8..f2c22cb15b9 100644 --- a/plugins/sonar-surefire-plugin/src/main/java/org/sonar/plugins/surefire/api/AbstractSurefireParser.java +++ b/plugins/sonar-surefire-plugin/src/main/java/org/sonar/plugins/surefire/api/AbstractSurefireParser.java @@ -91,7 +91,7 @@ public abstract class AbstractSurefireParser { for (String classname : index.getClassnames()) { if (StringUtils.contains(classname, "$")) { // Surefire reports classes whereas sonar supports files - String parentClassName = StringUtils.substringBeforeLast(classname, "$"); + String parentClassName = StringUtils.substringBefore(classname, "$"); index.merge(classname, parentClassName); } } diff --git a/plugins/sonar-surefire-plugin/src/main/java/org/sonar/plugins/surefire/data/SurefireStaxHandler.java b/plugins/sonar-surefire-plugin/src/main/java/org/sonar/plugins/surefire/data/SurefireStaxHandler.java index b9a0b3b9e41..38ebc7b3571 100644 --- a/plugins/sonar-surefire-plugin/src/main/java/org/sonar/plugins/surefire/data/SurefireStaxHandler.java +++ b/plugins/sonar-surefire-plugin/src/main/java/org/sonar/plugins/surefire/data/SurefireStaxHandler.java @@ -125,7 +125,7 @@ public class SurefireStaxHandler implements XmlStreamHandler { String classname = testCaseCursor.getAttrValue("classname"); String name = testCaseCursor.getAttrValue("name"); if (StringUtils.contains(classname, "$")) { - return StringUtils.substringAfterLast(classname, "$") + "/" + name; + return StringUtils.substringAfter(classname, "$") + "/" + name; } return name; } diff --git a/plugins/sonar-surefire-plugin/src/test/java/org/sonar/plugins/surefire/api/AbstractSurefireParserTest.java b/plugins/sonar-surefire-plugin/src/test/java/org/sonar/plugins/surefire/api/AbstractSurefireParserTest.java index e4d195ebe42..51c666a6843 100644 --- a/plugins/sonar-surefire-plugin/src/test/java/org/sonar/plugins/surefire/api/AbstractSurefireParserTest.java +++ b/plugins/sonar-surefire-plugin/src/test/java/org/sonar/plugins/surefire/api/AbstractSurefireParserTest.java @@ -111,6 +111,19 @@ public class AbstractSurefireParserTest { verify(context, never()).saveMeasure(argThat(new IsResource(Scopes.FILE, Qualifiers.FILE, "org.apache.commons.collections.bidimap.AbstractTestBidiMap$TestBidiMapEntrySet")), any(Metric.class), anyDouble()); } + @Test + public void shouldMergeNestedInnerClasses() throws URISyntaxException { + AbstractSurefireParser parser = newParser(); + + SensorContext context = mockContext(); + parser.collect(new Project("foo"), context, getDir("nestedInnerClasses")); + + verify(context).saveMeasure( + argThat(new IsResource(Scopes.FILE, Qualifiers.FILE, "org.sonar.plugins.surefire.NestedInnerTest")), + eq(CoreMetrics.TESTS), + eq(3.0)); + } + private AbstractSurefireParser newParser() { return new AbstractSurefireParser() { diff --git a/plugins/sonar-surefire-plugin/src/test/resources/org/sonar/plugins/surefire/api/AbstractSurefireParserTest/nestedInnerClasses/TEST-org.sonar.plugins.surefire.NestedTest$Inner1$Run.xml b/plugins/sonar-surefire-plugin/src/test/resources/org/sonar/plugins/surefire/api/AbstractSurefireParserTest/nestedInnerClasses/TEST-org.sonar.plugins.surefire.NestedTest$Inner1$Run.xml new file mode 100644 index 00000000000..b3357bd1f87 --- /dev/null +++ b/plugins/sonar-surefire-plugin/src/test/resources/org/sonar/plugins/surefire/api/AbstractSurefireParserTest/nestedInnerClasses/TEST-org.sonar.plugins.surefire.NestedTest$Inner1$Run.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<testsuite failures="0" time="0.001" errors="0" skipped="0" tests="1" name="org.sonar.plugins.surefire.NestedInnerTest"> + <properties> + <property name="java.runtime.name" value="Java(TM) SE Runtime Environment"/> + <property name="sun.boot.library.path" value="/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Libraries"/> + <property name="java.vm.version" value="20.1-b02-384"/> + <property name="awt.nativeDoubleBuffering" value="true"/> + <property name="gopherProxySet" value="false"/> + <property name="mrj.build" value="10M3425"/> + <property name="java.vm.vendor" value="Apple Inc."/> + <property name="java.vendor.url" value="http://www.apple.com/"/> + <property name="path.separator" value=":"/> + <property name="java.vm.name" value="Java HotSpot(TM) 64-Bit Server VM"/> + <property name="file.encoding.pkg" value="sun.io"/> + <property name="user.country" value="US"/> + <property name="sun.java.launcher" value="SUN_STANDARD"/> + <property name="sun.os.patch.level" value="unknown"/> + <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/> + <property name="user.dir" value="/Users/sbrandhof/projects/github/sonar/plugins/sonar-surefire-plugin"/> + <property name="java.runtime.version" value="1.6.0_26-b03-384-10M3425"/> + <property name="java.awt.graphicsenv" value="apple.awt.CGraphicsEnvironment"/> + <property name="java.endorsed.dirs" value="/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/endorsed"/> + <property name="os.arch" value="x86_64"/> + <property name="java.io.tmpdir" value="/var/folders/H1/H1p3aGauF5myqknnfgovp++++TI/-Tmp-/"/> + <property name="line.separator" value=" +"/> + <property name="java.vm.specification.vendor" value="Sun Microsystems Inc."/> + <property name="os.name" value="Mac OS X"/> + <property name="classworlds.conf" value="/Users/sbrandhof/Dropbox/dev/softwares/apache-maven-2.2.1/bin/m2.conf"/> + <property name="sun.jnu.encoding" value="MacRoman"/> + <property name="java.library.path" value=".:/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java"/> + <property name="java.specification.name" value="Java Platform API Specification"/> + <property name="java.class.version" value="50.0"/> + <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/> + <property name="os.version" value="10.6.8"/> + <property name="http.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/> + <property name="user.home" value="/Users/sbrandhof"/> + <property name="user.timezone" value="Europe/Paris"/> + <property name="java.awt.printerjob" value="apple.awt.CPrinterJob"/> + <property name="java.specification.version" value="1.6"/> + <property name="file.encoding" value="MacRoman"/> + <property name="user.name" value="sbrandhof"/> + <property name="java.class.path" value="/Users/sbrandhof/Dropbox/dev/softwares/apache-maven-2.2.1/boot/classworlds-1.1.jar"/> + <property name="java.vm.specification.version" value="1.0"/> + <property name="sun.arch.data.model" value="64"/> + <property name="java.home" value="/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home"/> + <property name="sun.java.command" value="org.codehaus.classworlds.Launcher "clean" "install""/> + <property name="java.specification.vendor" value="Sun Microsystems Inc."/> + <property name="user.language" value="en"/> + <property name="awt.toolkit" value="apple.awt.CToolkit"/> + <property name="java.vm.info" value="mixed mode"/> + <property name="java.version" value="1.6.0_26"/> + <property name="java.ext.dirs" value="/Library/Java/Extensions:/System/Library/Java/Extensions:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/ext"/> + <property name="sun.boot.class.path" value="/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/jsfd.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/classes.jar:/System/Library/Frameworks/JavaVM.framework/Frameworks/JavaRuntimeSupport.framework/Resources/Java/JavaRuntimeSupport.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/ui.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/laf.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/sunrsasign.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/jsse.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/jce.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/charsets.jar"/> + <property name="java.vendor" value="Apple Inc."/> + <property name="maven.home" value="/Users/sbrandhof/Dropbox/dev/softwares/apache-maven-2.2.1"/> + <property name="file.separator" value="/"/> + <property name="java.vendor.url.bug" value="http://bugreport.apple.com/"/> + <property name="sun.cpu.endian" value="little"/> + <property name="sun.io.unicode.encoding" value="UnicodeLittle"/> + <property name="mrj.version" value="1060.1.6.0_26-384"/> + <property name="socksNonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/> + <property name="ftp.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/> + <property name="sun.cpu.isalist" value=""/> + </properties> + <testcase time="0" classname="org.sonar.plugins.surefire.NestedInnerTest$Inner1$Run" name="test1"/> + <testcase time="0" classname="org.sonar.plugins.surefire.NestedInnerTest$Inner2$Run" name="test2"/> + <testcase time="0" classname="org.sonar.plugins.surefire.NestedInnerTest$Inner3$Run" name="test3"/> +</testsuite>
\ No newline at end of file diff --git a/plugins/sonar-surefire-plugin/src/test/resources/org/sonar/plugins/surefire/api/AbstractSurefireParserTest/nestedInnerClasses/TEST-org.sonar.plugins.surefire.NestedTest$Inner2$Run.xml b/plugins/sonar-surefire-plugin/src/test/resources/org/sonar/plugins/surefire/api/AbstractSurefireParserTest/nestedInnerClasses/TEST-org.sonar.plugins.surefire.NestedTest$Inner2$Run.xml new file mode 100644 index 00000000000..677686a7aa3 --- /dev/null +++ b/plugins/sonar-surefire-plugin/src/test/resources/org/sonar/plugins/surefire/api/AbstractSurefireParserTest/nestedInnerClasses/TEST-org.sonar.plugins.surefire.NestedTest$Inner2$Run.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<testsuite failures="0" time="0.023" errors="0" skipped="0" tests="1" name="org.sonar.plugins.surefire.NestedInnerTest$Inner2$Run"> + <properties> + <property name="java.runtime.name" value="Java(TM) SE Runtime Environment"/> + <property name="sun.boot.library.path" value="/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Libraries"/> + <property name="java.vm.version" value="20.1-b02-384"/> + <property name="awt.nativeDoubleBuffering" value="true"/> + <property name="gopherProxySet" value="false"/> + <property name="mrj.build" value="10M3425"/> + <property name="java.vm.vendor" value="Apple Inc."/> + <property name="java.vendor.url" value="http://www.apple.com/"/> + <property name="path.separator" value=":"/> + <property name="java.vm.name" value="Java HotSpot(TM) 64-Bit Server VM"/> + <property name="file.encoding.pkg" value="sun.io"/> + <property name="user.country" value="US"/> + <property name="sun.java.launcher" value="SUN_STANDARD"/> + <property name="sun.os.patch.level" value="unknown"/> + <property name="java.vm.specification.name" value="Java Virtual Machine Specification"/> + <property name="user.dir" value="/Users/sbrandhof/projects/github/sonar/plugins/sonar-surefire-plugin"/> + <property name="java.runtime.version" value="1.6.0_26-b03-384-10M3425"/> + <property name="java.awt.graphicsenv" value="apple.awt.CGraphicsEnvironment"/> + <property name="java.endorsed.dirs" value="/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/endorsed"/> + <property name="os.arch" value="x86_64"/> + <property name="java.io.tmpdir" value="/var/folders/H1/H1p3aGauF5myqknnfgovp++++TI/-Tmp-/"/> + <property name="line.separator" value=" +"/> + <property name="java.vm.specification.vendor" value="Sun Microsystems Inc."/> + <property name="os.name" value="Mac OS X"/> + <property name="classworlds.conf" value="/Users/sbrandhof/Dropbox/dev/softwares/apache-maven-2.2.1/bin/m2.conf"/> + <property name="sun.jnu.encoding" value="MacRoman"/> + <property name="java.library.path" value=".:/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java"/> + <property name="java.specification.name" value="Java Platform API Specification"/> + <property name="java.class.version" value="50.0"/> + <property name="sun.management.compiler" value="HotSpot 64-Bit Tiered Compilers"/> + <property name="os.version" value="10.6.8"/> + <property name="http.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/> + <property name="user.home" value="/Users/sbrandhof"/> + <property name="user.timezone" value="Europe/Paris"/> + <property name="java.awt.printerjob" value="apple.awt.CPrinterJob"/> + <property name="java.specification.version" value="1.6"/> + <property name="file.encoding" value="MacRoman"/> + <property name="user.name" value="sbrandhof"/> + <property name="java.class.path" value="/Users/sbrandhof/Dropbox/dev/softwares/apache-maven-2.2.1/boot/classworlds-1.1.jar"/> + <property name="java.vm.specification.version" value="1.0"/> + <property name="sun.arch.data.model" value="64"/> + <property name="java.home" value="/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home"/> + <property name="sun.java.command" value="org.codehaus.classworlds.Launcher "clean" "install""/> + <property name="java.specification.vendor" value="Sun Microsystems Inc."/> + <property name="user.language" value="en"/> + <property name="awt.toolkit" value="apple.awt.CToolkit"/> + <property name="java.vm.info" value="mixed mode"/> + <property name="java.version" value="1.6.0_26"/> + <property name="java.ext.dirs" value="/Library/Java/Extensions:/System/Library/Java/Extensions:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/ext"/> + <property name="sun.boot.class.path" value="/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/jsfd.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/classes.jar:/System/Library/Frameworks/JavaVM.framework/Frameworks/JavaRuntimeSupport.framework/Resources/Java/JavaRuntimeSupport.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/ui.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/laf.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/sunrsasign.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/jsse.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/jce.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/charsets.jar"/> + <property name="java.vendor" value="Apple Inc."/> + <property name="maven.home" value="/Users/sbrandhof/Dropbox/dev/softwares/apache-maven-2.2.1"/> + <property name="file.separator" value="/"/> + <property name="java.vendor.url.bug" value="http://bugreport.apple.com/"/> + <property name="sun.cpu.endian" value="little"/> + <property name="sun.io.unicode.encoding" value="UnicodeLittle"/> + <property name="mrj.version" value="1060.1.6.0_26-384"/> + <property name="socksNonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/> + <property name="ftp.nonProxyHosts" value="local|*.local|169.254/16|*.169.254/16"/> + <property name="sun.cpu.isalist" value=""/> + </properties> + <testcase time="0" classname="org.sonar.plugins.surefire.NestedInnerTest$Inner2$Run" name="test2"/> +</testsuite>
\ No newline at end of file |