@@ -5,7 +5,7 @@ | |||
<parent> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar</artifactId> | |||
<version>2.10</version> | |||
<version>2.11</version> | |||
<relativePath>../..</relativePath> | |||
</parent> | |||
@@ -5,7 +5,7 @@ | |||
<parent> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar</artifactId> | |||
<version>2.10</version> | |||
<version>2.11</version> | |||
<relativePath>../..</relativePath> | |||
</parent> | |||
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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); | |||
@@ -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; | |||
} |
@@ -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> |
@@ -44,13 +44,22 @@ import org.sonar.plugins.core.widgets.*; | |||
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); |
@@ -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); | |||
} | |||
} |
@@ -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)) { |
@@ -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"; | |||
} | |||
} |
@@ -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"; | |||
} | |||
} |
@@ -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 | |||
%> | |||
<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> |
@@ -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> |
@@ -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 %> |
@@ -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); |
@@ -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"); |
@@ -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> |
@@ -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(); | |||
} | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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 |
@@ -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(); | |||
} | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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); |
@@ -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"); | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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); | |||
} | |||
@@ -96,6 +103,13 @@ public final class PurgeUtils { | |||
executeQuery(session, "delete violations", snapshotIds, "delete from " + RuleFailureModel.class.getSimpleName() + " e where e.snapshotId in (:ids)"); | |||
} | |||
/** | |||
* @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 | |||
*/ |
@@ -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> | |||
<!--<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> |
@@ -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> | |||
<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> |
@@ -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> |
@@ -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); |
@@ -5,7 +5,7 @@ | |||
<parent> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar</artifactId> | |||
<version>2.10</version> | |||
<version>2.11</version> | |||
<relativePath>../..</relativePath> | |||
</parent> | |||
@@ -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> |
@@ -5,7 +5,7 @@ | |||
<parent> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar</artifactId> | |||
<version>2.10</version> | |||
<version>2.11</version> | |||
<relativePath>../..</relativePath> | |||
</parent> | |||
@@ -5,7 +5,7 @@ | |||
<parent> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar</artifactId> | |||
<version>2.10</version> | |||
<version>2.11</version> | |||
<relativePath>../..</relativePath> | |||
</parent> | |||
@@ -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. | |||
#------------------------------------------------------------------------------ | |||
@@ -414,6 +420,23 @@ dashboard.edit_dashboard=Edit dashboard | |||
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 | |||
@@ -567,6 +597,25 @@ manual_measures.save_and_add_button=Save & Add new | |||
manual_measures.pending_message=Pending measures are marked with orange box. Their values will be integrated to project during next analysis. | |||
#------------------------------------------------------------------------------ | |||
# | |||
# 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,11 +756,28 @@ 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 |
@@ -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> |
@@ -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> |
@@ -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); |
@@ -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, |
@@ -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/"); |
@@ -5,7 +5,7 @@ | |||
<parent> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar</artifactId> | |||
<version>2.10</version> | |||
<version>2.11</version> | |||
<relativePath>../..</relativePath> | |||
</parent> | |||
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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() { |
@@ -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> |
@@ -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> |
@@ -5,7 +5,7 @@ | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar</artifactId> | |||
<packaging>pom</packaging> | |||
<version>2.10</version> | |||
<version>2.11</version> | |||
<name>Sonar</name> | |||
<url>http://www.sonarsource.org</url> | |||
<description>Put technical debt under control</description> | |||
@@ -209,6 +209,10 @@ | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-javadoc-plugin</artifactId> | |||
<version>2.8</version> | |||
<configuration> | |||
<author>false</author> | |||
<linksource>true</linksource> | |||
</configuration> | |||
</plugin> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
@@ -272,6 +276,7 @@ | |||
<version>1.1</version> | |||
<extensions>true</extensions> | |||
</plugin> | |||
</plugins> | |||
</pluginManagement> | |||
@@ -388,17 +393,6 @@ | |||
</archive> | |||
</configuration> | |||
</plugin> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-javadoc-plugin</artifactId> | |||
<configuration> | |||
<excludePackageNames> | |||
net.* | |||
</excludePackageNames> | |||
<author>false</author> | |||
<linksource>true</linksource> | |||
</configuration> | |||
</plugin> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-jxr-plugin</artifactId> | |||
@@ -469,6 +463,26 @@ | |||
<runOrder>random</runOrder> | |||
</configuration> | |||
</plugin> | |||
<plugin> | |||
<!-- this plugin is used to list the licenses of all the dependencies : | |||
mvn org.codehaus.mojo:license-maven-plugin:aggregate-add-third-party | |||
--> | |||
<groupId>org.codehaus.mojo</groupId> | |||
<artifactId>license-maven-plugin</artifactId> | |||
<version>1.0-beta-3</version> | |||
<configuration> | |||
<groupByLicense>true</groupByLicense> | |||
<licenseMerges> | |||
<licenseMerge>The Apache Software License, Version 2.0|Apache License 2.0|Apache 2|Apache License Version 2|Apache License Version 2.0|Apache Public License 2.0|Apache Software License - Version 2.0|Apache Software Licenses</licenseMerge> | |||
<licenseMerge>GNU Lesser General Public License, Version 2.1|GNU Lesser General Public License Version 2.1|GNU Lesser General Public License (LGPL), Version 2.1</licenseMerge> | |||
<licenseMerge>BSD|BSD License|The BSD License</licenseMerge> | |||
<licenseMerge>Common Public License, Version 1.0|Common Public License - v 1.0|Common Public License Version 1.0</licenseMerge> | |||
<licenseMerge>Eclipse Public License, Version 1.0|Eclipse Public License - Version 1.0</licenseMerge> | |||
<licenseMerge>Common Development and Distribution License, Version 1.0|COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0|Common Development and Distribution License (CDDL) v1.0</licenseMerge> | |||
<licenseMerge>MIT License|The MIT License</licenseMerge> | |||
</licenseMerges> | |||
</configuration> | |||
</plugin> | |||
</plugins> | |||
</build> | |||
@@ -863,7 +877,7 @@ | |||
<dependency> | |||
<groupId>org.jruby</groupId> | |||
<artifactId>jruby-complete</artifactId> | |||
<version>1.6.1</version> | |||
<version>1.6.4</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>geronimo-spec</groupId> | |||
@@ -1083,6 +1097,26 @@ | |||
</plugins> | |||
</build> | |||
</profile> | |||
<profile> | |||
<id>javadoc</id> | |||
<build> | |||
<plugins> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-javadoc-plugin</artifactId> | |||
<configuration> | |||
<excludePackageNames> | |||
net.*:org.sonar.application:org.sonar.server:org.sonar.graph:org.sonar.batch:org.sonar.channel:org.sonar.java:org.sonar.maven*:org.sonar.plugins.*:org.sonar.colorizer:org.sonar.squid:org.sonar.core:org.sonar.jpa:org.sonar.duplications:org.sonar.markdown:com.* | |||
</excludePackageNames> | |||
<author>false</author> | |||
<linksource>true</linksource> | |||
<reportOutputDirectory>${project.reporting.outputDirectory}/${project.version}/apidocs</reportOutputDirectory> | |||
</configuration> | |||
</plugin> | |||
</plugins> | |||
</build> | |||
</profile> | |||
<profile> | |||
<id>sanity-checks</id> |
@@ -5,7 +5,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.samples</groupId> |
@@ -4,7 +4,7 @@ | |||
<parent> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar</artifactId> | |||
<version>2.10</version> | |||
<version>2.11</version> | |||
</parent> | |||
<artifactId>sonar-application</artifactId> | |||
<packaging>jar</packaging> |
@@ -25,15 +25,14 @@ | |||
#sonar.ajp13.port: 8009 | |||
#--------------------------------------------------------- | |||
#----------------------------------------------------------------------- | |||
# DATABASE | |||
# | |||
# IMPORTANT : the embedded database Derby is used by default. | |||
# It is recommended for tests only. Please use an other database | |||
# for production environment (MySQL, Oracle, Postgresql, | |||
# SQLServer) | |||
# It is recommended for tests only. Please use an external database | |||
# for production environment (MySQL, Oracle, Postgresql, SQLServer) | |||
# | |||
#--------------------------------------------------------- | |||
#----------------------------------------------------------------------- | |||
#----- Credentials | |||
# Permissions to create tables and indexes must be granted to JDBC user. | |||
@@ -66,15 +65,20 @@ sonar.jdbc.driverClassName: org.apache.derby.jdbc.ClientDriver | |||
#----- Oracle 10g/11g | |||
# To use Oracle database : | |||
# - Copy the JDBC driver to extensions/jdbc-driver/oracle. | |||
# To connect to Oracle database : | |||
# | |||
# - It's recommended to use the latest version of the JDBC driver (either ojdbc6.jar for Java 6 or ojdbc5.jar for Java 5). | |||
# Download it in http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-112010-090769.html | |||
# - Copy the driver to the directory extensions/jdbc-driver/oracle/ | |||
# - Comment the embedded database and uncomment the following properties. The validation query is optional. | |||
# | |||
#sonar.jdbc.url: jdbc:oracle:thin:@localhost/XE | |||
#sonar.jdbc.driverClassName: oracle.jdbc.driver.OracleDriver | |||
#sonar.jdbc.validationQuery: select 1 from dual | |||
# Activate if more than one Sonar Oracle schemas on the data server (for example different versions installed). | |||
# In that case, use the same property during maven analysis (-Dsonar.hibernate.default_schema=xxx) | |||
# Uncomment the following property if multiple Sonar schemas are installed on the same server | |||
# (for example with different versions). | |||
# In that case, use the same property during project analysis (-Dsonar.hibernate.default_schema=<schema>) | |||
#sonar.hibernate.default_schema: sonar | |||
#----- PostgreSQL 8.x, 9.x | |||
@@ -83,6 +87,9 @@ sonar.jdbc.driverClassName: org.apache.derby.jdbc.ClientDriver | |||
#sonar.jdbc.driverClassName: org.postgresql.Driver | |||
#sonar.jdbc.validationQuery: select 1 | |||
# If the PostgreSql database contains several schemas, the following property must define the schema to be used | |||
#sonar.jdbc.postgreSearchPath: public | |||
#----- Microsoft SQLServer | |||
# The Jtds open source driver is available in extensions/jdbc-driver/mssql. More details on http://jtds.sourceforge.net | |||
@@ -93,7 +100,7 @@ sonar.jdbc.driverClassName: org.apache.derby.jdbc.ClientDriver | |||
#----- Connection pool settings | |||
sonar.jdbc.maxActive: 10 | |||
sonar.jdbc.maxActive: 20 | |||
sonar.jdbc.maxIdle: 5 | |||
sonar.jdbc.minIdle: 2 | |||
sonar.jdbc.maxWait: 5000 | |||
@@ -101,17 +108,6 @@ sonar.jdbc.minEvictableIdleTimeMillis: 600000 | |||
sonar.jdbc.timeBetweenEvictionRunsMillis: 30000 | |||
#----- JDBC Datasource bounded to JNDI | |||
# When sonar webapp is deployed into a JEE server, the JDBC datasource can be configured into the JEE server and registered into JNDI. | |||
# In such a case Sonar uses this datasource to connect to database. | |||
# If you activate this feature, then the properties starting with "sonar.jdbc." can be commented, except "sonar.jdbc.dialect". | |||
# The JDBC driver must still be deployed into the directory /extensions/jdbc-driver. | |||
#sonar.jdbc.jndiName: jdbc/sonar | |||
# Values are : mysql, mssql, derby, oracle, postgresql | |||
#sonar.jdbc.dialect= | |||
#--------------------------------------------------------- | |||
# UPDATE CENTER | |||
#--------------------------------------------------------- |
@@ -5,7 +5,7 @@ | |||
<parent> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar</artifactId> | |||
<version>2.10</version> | |||
<version>2.11</version> | |||
</parent> | |||
<artifactId>sonar-batch-bootstrapper</artifactId> |
@@ -5,7 +5,7 @@ | |||
<parent> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar</artifactId> | |||
<version>2.10</version> | |||
<version>2.11</version> | |||
</parent> | |||
<artifactId>sonar-batch-maven-compat</artifactId> |
@@ -4,7 +4,7 @@ | |||
<parent> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar</artifactId> | |||
<version>2.10</version> | |||
<version>2.11</version> | |||
</parent> | |||
<groupId>org.codehaus.sonar</groupId> |
@@ -35,17 +35,14 @@ public abstract class AbstractMavenPluginExecutor implements MavenPluginExecutor | |||
public final MavenPluginHandler execute(Project project, ProjectDefinition projectDefinition, MavenPluginHandler handler) { | |||
for (String goal : handler.getGoals()) { | |||
MavenPlugin plugin = MavenPlugin.getPlugin(project.getPom(), handler.getGroupId(), handler.getArtifactId()); | |||
execute(project, getGoal(handler.getGroupId(), handler.getArtifactId(), (plugin!=null && plugin.getPlugin()!=null ? plugin.getPlugin().getVersion() : null), goal)); | |||
execute(project, | |||
projectDefinition, | |||
getGoal(handler.getGroupId(), handler.getArtifactId(), (plugin != null && plugin.getPlugin() != null ? plugin.getPlugin().getVersion() : null), goal)); | |||
} | |||
if (project.getPom()!=null) { | |||
MavenProjectConverter.synchronizeFileSystem(project.getPom(), projectDefinition); | |||
} | |||
return handler; | |||
} | |||
public final void execute(Project project, String goal) { | |||
public final void execute(Project project, ProjectDefinition projectDefinition, String goal) { | |||
TimeProfiler profiler = new TimeProfiler().start("Execute " + goal); | |||
ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); | |||
try { | |||
@@ -57,6 +54,10 @@ public abstract class AbstractMavenPluginExecutor implements MavenPluginExecutor | |||
Thread.currentThread().setContextClassLoader(currentClassLoader); | |||
profiler.stop(); | |||
} | |||
if (project.getPom() != null) { | |||
MavenProjectConverter.synchronizeFileSystem(project.getPom(), projectDefinition); | |||
} | |||
} | |||
public abstract void concreteExecute(MavenProject pom, String goal) throws Exception; |
@@ -24,7 +24,7 @@ import org.sonar.api.batch.maven.MavenPluginHandler; | |||
import org.sonar.api.resources.Project; | |||
public final class FakeMavenPluginExecutor implements MavenPluginExecutor { | |||
public void execute(Project project, String goal) { | |||
public void execute(Project project, ProjectDefinition projectDef, String goal) { | |||
// do nothing | |||
} | |||
@@ -26,7 +26,7 @@ import org.sonar.api.resources.Project; | |||
public interface MavenPluginExecutor extends BatchComponent { | |||
void execute(Project project, String goal); | |||
void execute(Project project, ProjectDefinition def, String goal); | |||
MavenPluginHandler execute(Project project, ProjectDefinition def, MavenPluginHandler handler); | |||
@@ -31,43 +31,39 @@ import java.util.Date; | |||
public class ServerMetadata extends Server { | |||
private String id; | |||
private String version; | |||
private String url; | |||
private Date startTime; | |||
private Configuration conf; | |||
public ServerMetadata(Configuration conf) { | |||
id = conf.getString(CoreProperties.SERVER_ID); | |||
version = conf.getString(CoreProperties.SERVER_VERSION); | |||
url = getURL(conf); | |||
String dateString = conf.getString(CoreProperties.SERVER_STARTTIME); | |||
if (dateString!=null) { | |||
try { | |||
this.startTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(dateString); | |||
} catch (ParseException e) { | |||
LoggerFactory.getLogger(getClass()).error("The property " + CoreProperties.SERVER_STARTTIME + " is badly formatted.", e); | |||
} | |||
} | |||
} | |||
public static String getURL(Configuration conf) { | |||
return StringUtils.removeEnd(conf.getString("sonar.host.url", "http://localhost:9000"), "/"); | |||
this.conf = conf; | |||
} | |||
public String getId() { | |||
return id; | |||
return conf.getString(CoreProperties.SERVER_ID); | |||
} | |||
public String getVersion() { | |||
return version; | |||
return conf.getString(CoreProperties.SERVER_VERSION); | |||
} | |||
public Date getStartedAt() { | |||
return startTime; | |||
String dateString = conf.getString(CoreProperties.SERVER_STARTTIME); | |||
if (dateString != null) { | |||
try { | |||
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(dateString); | |||
} catch (ParseException e) { | |||
LoggerFactory.getLogger(getClass()).error("The property " + CoreProperties.SERVER_STARTTIME + " is badly formatted.", e); | |||
} | |||
} | |||
return null; | |||
} | |||
public String getURL() { | |||
return url; | |||
return StringUtils.removeEnd(conf.getString("sonar.host.url", "http://localhost:9000"), "/"); | |||
} | |||
@Override | |||
public String getPermanentServerId() { | |||
return conf.getString(CoreProperties.PERMANENT_SERVER_ID); | |||
} | |||
} |
@@ -64,12 +64,14 @@ public final class BatchExtensionInstaller implements BatchComponent { | |||
List<ExtensionProvider> providers = module.getComponents(ExtensionProvider.class); | |||
for (ExtensionProvider provider : providers) { | |||
Object obj = provider.provide(); | |||
if (obj instanceof Iterable) { | |||
for (Object extension : (Iterable) obj) { | |||
installExtension(module, extension); | |||
if (obj != null) { | |||
if (obj instanceof Iterable) { | |||
for (Object extension : (Iterable) obj) { | |||
installExtension(module, extension); | |||
} | |||
} else { | |||
installExtension(module, obj); | |||
} | |||
} else { | |||
installExtension(module, obj); | |||
} | |||
} | |||
} |
@@ -46,7 +46,7 @@ public final class EventPersister { | |||
} | |||
public void deleteEvent(Event event) { | |||
session.remove(event); | |||
session.removeWithoutFlush(event); | |||
session.commit(); | |||
} | |||
@@ -21,6 +21,7 @@ package org.sonar.batch.phases; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.BatchComponent; | |||
import org.sonar.api.batch.bootstrap.ProjectDefinition; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.batch.MavenPluginExecutor; | |||
@@ -29,15 +30,17 @@ public class MavenPhaseExecutor implements BatchComponent { | |||
public static final String PROP_PHASE = "sonar.phase"; | |||
private MavenPluginExecutor executor; | |||
private ProjectDefinition projectDef; | |||
public MavenPhaseExecutor(MavenPluginExecutor executor) { | |||
public MavenPhaseExecutor(ProjectDefinition projectDef, MavenPluginExecutor executor) { | |||
this.projectDef = projectDef; | |||
this.executor = executor; | |||
} | |||
public void execute(Project project) { | |||
String mavenPhase = (String) project.getProperty(PROP_PHASE); | |||
if (!StringUtils.isBlank(mavenPhase)) { | |||
executor.execute(project, mavenPhase); | |||
executor.execute(project, projectDef, mavenPhase); | |||
} | |||
} | |||
} |
@@ -19,6 +19,9 @@ | |||
*/ | |||
package org.sonar.batch.bootstrap; | |||
import static org.hamcrest.Matchers.is; | |||
import static org.junit.Assert.assertThat; | |||
import org.junit.Test; | |||
import org.sonar.api.batch.bootstrap.ProjectDefinition; | |||
import org.sonar.api.batch.bootstrap.ProjectReactor; | |||
@@ -26,13 +29,10 @@ import org.sonar.api.batch.maven.MavenPluginHandler; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.batch.MavenPluginExecutor; | |||
import static org.hamcrest.Matchers.is; | |||
import static org.junit.Assert.assertThat; | |||
public class BootstrapModuleTest { | |||
class MyMavenPluginExecutor implements MavenPluginExecutor { | |||
public void execute(Project project, String goal) { | |||
public void execute(Project project, ProjectDefinition projectDef, String goal) { | |||
} | |||
public MavenPluginHandler execute(Project project, ProjectDefinition projectDef, MavenPluginHandler handler) { |
@@ -19,32 +19,38 @@ | |||
*/ | |||
package org.sonar.batch.phases; | |||
import static org.mockito.Matchers.anyString; | |||
import static org.mockito.Matchers.eq; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.never; | |||
import static org.mockito.Mockito.verify; | |||
import org.apache.commons.configuration.PropertiesConfiguration; | |||
import org.junit.Test; | |||
import org.sonar.api.batch.bootstrap.ProjectDefinition; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.batch.MavenPluginExecutor; | |||
import static org.mockito.Matchers.anyString; | |||
import static org.mockito.Mockito.*; | |||
public class MavenPhaseExecutorTest { | |||
@Test | |||
public void doNothingIfNoPhase() { | |||
ProjectDefinition projectDef = ProjectDefinition.create(); | |||
MavenPluginExecutor mavenPluginExecutor = mock(MavenPluginExecutor.class); | |||
MavenPhaseExecutor phaseExecutor = new MavenPhaseExecutor(mavenPluginExecutor); | |||
MavenPhaseExecutor phaseExecutor = new MavenPhaseExecutor(projectDef, mavenPluginExecutor); | |||
Project project = new Project("key"); | |||
phaseExecutor.execute(project); | |||
verify(mavenPluginExecutor, never()).execute(eq(project), anyString()); | |||
verify(mavenPluginExecutor, never()).execute(eq(project), eq(projectDef), anyString()); | |||
} | |||
@Test | |||
public void executePhase() { | |||
ProjectDefinition projectDef = ProjectDefinition.create(); | |||
MavenPluginExecutor mavenPluginExecutor = mock(MavenPluginExecutor.class); | |||
MavenPhaseExecutor phaseExecutor = new MavenPhaseExecutor(mavenPluginExecutor); | |||
MavenPhaseExecutor phaseExecutor = new MavenPhaseExecutor(projectDef, mavenPluginExecutor); | |||
Project project = new Project("key"); | |||
PropertiesConfiguration conf = new PropertiesConfiguration(); | |||
@@ -53,6 +59,6 @@ public class MavenPhaseExecutorTest { | |||
phaseExecutor.execute(project); | |||
verify(mavenPluginExecutor).execute(project, "myphase"); | |||
verify(mavenPluginExecutor).execute(project, projectDef, "myphase"); | |||
} | |||
} |
@@ -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-channel</artifactId> |
@@ -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-check-api</artifactId> |
@@ -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-colorizer</artifactId> |
@@ -4,7 +4,7 @@ | |||
<parent> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar</artifactId> | |||
<version>2.10</version> | |||
<version>2.11</version> | |||
</parent> | |||
<!-- do not change, it's used when deploying in sonar internal repository --> | |||
@@ -36,29 +36,6 @@ | |||
--> | |||
<dependency> | |||
<groupId>org.apache.maven.shared</groupId> | |||
<artifactId>maven-dependency-tree</artifactId> | |||
<version>1.2</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar-plugin-api</artifactId> | |||
<version>${project.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar-batch</artifactId> | |||
<version>${project.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.slf4j</groupId> | |||
<artifactId>slf4j-api</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>ch.qos.logback</groupId> | |||
<artifactId>logback-classic</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.apache.maven</groupId> | |||
<artifactId>maven-plugin-api</artifactId> | |||
@@ -77,17 +54,6 @@ | |||
<version>2.0.7</version> | |||
<scope>provided</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>commons-lang</groupId> | |||
<artifactId>commons-lang</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar-plugin-api</artifactId> | |||
<version>${project.version}</version> | |||
<scope>test</scope> | |||
<type>test-jar</type> | |||
</dependency> | |||
</dependencies> | |||
<build> | |||
<plugins> |
@@ -19,33 +19,9 @@ | |||
*/ | |||
package org.sonar.maven2; | |||
import ch.qos.logback.classic.LoggerContext; | |||
import ch.qos.logback.classic.joran.JoranConfigurator; | |||
import ch.qos.logback.core.joran.spi.JoranException; | |||
import org.apache.commons.configuration.*; | |||
import org.apache.commons.io.IOUtils; | |||
import org.apache.maven.artifact.factory.ArtifactFactory; | |||
import org.apache.maven.artifact.metadata.ArtifactMetadataSource; | |||
import org.apache.maven.artifact.repository.ArtifactRepository; | |||
import org.apache.maven.artifact.resolver.ArtifactCollector; | |||
import org.apache.maven.execution.MavenSession; | |||
import org.apache.maven.execution.RuntimeInformation; | |||
import org.apache.maven.lifecycle.LifecycleExecutor; | |||
import org.apache.maven.plugin.AbstractMojo; | |||
import org.apache.maven.plugin.MojoExecutionException; | |||
import org.apache.maven.plugin.MojoFailureException; | |||
import org.apache.maven.plugin.PluginManager; | |||
import org.apache.maven.project.MavenProject; | |||
import org.apache.maven.project.MavenProjectBuilder; | |||
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder; | |||
import org.slf4j.LoggerFactory; | |||
import org.sonar.api.batch.bootstrap.ProjectDefinition; | |||
import org.sonar.api.batch.bootstrap.ProjectReactor; | |||
import org.sonar.batch.Batch; | |||
import org.sonar.batch.MavenProjectConverter; | |||
import org.sonar.batch.bootstrapper.EnvironmentInformation; | |||
import java.io.InputStream; | |||
/** | |||
* @goal internal | |||
@@ -54,135 +30,8 @@ import java.io.InputStream; | |||
*/ | |||
public final class BatchMojo extends AbstractMojo { | |||
/** | |||
* @parameter expression="${session}" | |||
* @required | |||
* @readonly | |||
*/ | |||
private MavenSession session; | |||
/** | |||
* @parameter expression="${project}" | |||
* @required | |||
* @readonly | |||
*/ | |||
private MavenProject project; | |||
/** | |||
* @component | |||
* @required | |||
*/ | |||
private LifecycleExecutor lifecycleExecutor; | |||
/** | |||
* @component | |||
* @required | |||
*/ | |||
private PluginManager pluginManager; | |||
/** | |||
* The artifact factory to use. | |||
* | |||
* @component | |||
* @required | |||
* @readonly | |||
*/ | |||
private ArtifactFactory artifactFactory; | |||
/** | |||
* The artifact repository to use. | |||
* | |||
* @parameter expression="${localRepository}" | |||
* @required | |||
* @readonly | |||
*/ | |||
private ArtifactRepository localRepository; | |||
/** | |||
* The artifact metadata source to use. | |||
* | |||
* @component | |||
* @required | |||
* @readonly | |||
*/ | |||
private ArtifactMetadataSource artifactMetadataSource; | |||
/** | |||
* The artifact collector to use. | |||
* | |||
* @component | |||
* @required | |||
* @readonly | |||
*/ | |||
private ArtifactCollector artifactCollector; | |||
/** | |||
* The dependency tree builder to use. | |||
* | |||
* @component | |||
* @required | |||
* @readonly | |||
*/ | |||
private DependencyTreeBuilder dependencyTreeBuilder; | |||
/** | |||
* @component | |||
* @required | |||
* @readonly | |||
*/ | |||
private MavenProjectBuilder projectBuilder; | |||
/** | |||
* @component | |||
* @required | |||
* @readonly | |||
*/ | |||
private RuntimeInformation runtimeInformation; | |||
public void execute() throws MojoExecutionException, MojoFailureException { | |||
initLogging(); | |||
executeBatch(); | |||
} | |||
private void executeBatch() throws MojoExecutionException { | |||
ProjectDefinition def = MavenProjectConverter.convert(session.getSortedProjects(), project); | |||
ProjectReactor reactor = new ProjectReactor(def); | |||
Batch batch = Batch.create(reactor, getInitialConfiguration(), session, project, | |||
getLog(), lifecycleExecutor, pluginManager, artifactFactory, | |||
localRepository, artifactMetadataSource, artifactCollector, | |||
dependencyTreeBuilder, projectBuilder, getEnvironmentInformation(), Maven2PluginExecutor.class); | |||
batch.execute(); | |||
} | |||
private EnvironmentInformation getEnvironmentInformation() { | |||
String mavenVersion = runtimeInformation.getApplicationVersion().toString(); | |||
return new EnvironmentInformation("Maven", mavenVersion); | |||
} | |||
private Configuration getInitialConfiguration() { | |||
CompositeConfiguration configuration = new CompositeConfiguration(); | |||
configuration.addConfiguration(new SystemConfiguration()); | |||
configuration.addConfiguration(new EnvironmentConfiguration()); | |||
configuration.addConfiguration(new MapConfiguration(project.getModel().getProperties())); | |||
return configuration; | |||
} | |||
private void initLogging() throws MojoExecutionException { | |||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); | |||
JoranConfigurator jc = new JoranConfigurator(); | |||
jc.setContext(context); | |||
context.reset(); | |||
InputStream input = getClass().getResourceAsStream("/org/sonar/batch/logback.xml"); | |||
System.setProperty("ROOT_LOGGER_LEVEL", getLog().isDebugEnabled() ? "DEBUG" : "INFO"); | |||
try { | |||
jc.doConfigure(input); | |||
} catch (JoranException e) { | |||
throw new MojoExecutionException("can not initialize logging", e); | |||
} finally { | |||
IOUtils.closeQuietly(input); | |||
} | |||
throw new MojoExecutionException("The version 1.0-beta-1 of org.codehaus.mojo:sonar-maven-plugin is not supported anymore. " | |||
+ "Please upgrade to 1.0-beta-2 for Maven2 or 2.0-beta-2 for Maven3."); | |||
} | |||
} |
@@ -1,55 +0,0 @@ | |||
/* | |||
* 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.maven2; | |||
import org.apache.maven.execution.MavenSession; | |||
import org.apache.maven.execution.ReactorManager; | |||
import org.apache.maven.lifecycle.LifecycleExecutor; | |||
import org.apache.maven.project.MavenProject; | |||
import org.sonar.batch.AbstractMavenPluginExecutor; | |||
import java.util.Arrays; | |||
public class Maven2PluginExecutor extends AbstractMavenPluginExecutor { | |||
private LifecycleExecutor lifecycleExecutor; | |||
private MavenSession mavenSession; | |||
public Maven2PluginExecutor(LifecycleExecutor le, MavenSession mavenSession) { | |||
this.lifecycleExecutor = le; | |||
this.mavenSession = mavenSession; | |||
} | |||
@Override | |||
public void concreteExecute(MavenProject pom, String goal) throws Exception { | |||
ReactorManager reactor = new ReactorManager(Arrays.asList(pom)); | |||
MavenSession clonedSession = new MavenSession(mavenSession.getContainer(), | |||
mavenSession.getSettings(), | |||
mavenSession.getLocalRepository(), | |||
mavenSession.getEventDispatcher(), | |||
reactor, | |||
Arrays.asList(goal), | |||
mavenSession.getExecutionRootDirectory(), | |||
mavenSession.getExecutionProperties(), | |||
mavenSession.getStartTime()); | |||
lifecycleExecutor.execute(clonedSession, reactor, clonedSession.getEventDispatcher()); | |||
} | |||
} |
@@ -1,23 +0,0 @@ | |||
<?xml version="1.0" encoding="UTF-8" ?> | |||
<configuration> | |||
<appender name="STDOUT" | |||
class="ch.qos.logback.core.ConsoleAppender"> | |||
<layout class="ch.qos.logback.classic.PatternLayout"> | |||
<pattern> | |||
%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n | |||
</pattern> | |||
</layout> | |||
</appender> | |||
<logger name="org.sonar.DBSTATISTICS"> | |||
<level value="INFO"/> | |||
</logger> | |||
<root> | |||
<level value="WARN"/> | |||
<appender-ref ref="STDOUT"/> | |||
</root> | |||
</configuration> |
@@ -4,7 +4,7 @@ | |||
<parent> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar</artifactId> | |||
<version>2.10</version> | |||
<version>2.11</version> | |||
</parent> | |||
<artifactId>sonar-core</artifactId> | |||
<name>Sonar :: Core</name> |
@@ -48,6 +48,11 @@ public class DatabaseConfiguration extends BaseConfiguration { | |||
public void load() { | |||
clear(); | |||
// Ugly workaround before the move to myBatis | |||
// Session is not up-to-date when Ruby on Rails inserts new rows in its own transaction. Seems like | |||
// Hibernate keeps a cache... | |||
getSession().commit(); | |||
List<Property> properties = getSession() | |||
.createQuery("from " + Property.class.getSimpleName() + " p where p.resourceId is null and p.userId is null") | |||
.getResultList(); |
@@ -19,7 +19,7 @@ | |||
*/ | |||
package org.sonar.api.database.configuration; | |||
import org.apache.commons.lang.builder.ToStringBuilder; | |||
import org.apache.commons.lang.builder.ReflectionToStringBuilder; | |||
import org.apache.commons.lang.builder.ToStringStyle; | |||
import org.sonar.api.database.BaseIdentifiable; | |||
@@ -139,11 +139,6 @@ public class Property extends BaseIdentifiable { | |||
@Override | |||
public String toString() { | |||
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) | |||
.append("key", key) | |||
.append("resource", resourceId) | |||
.append("user", userId) | |||
.append("value", value) | |||
.toString(); | |||
return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString(); | |||
} | |||
} |
@@ -23,6 +23,7 @@ import com.google.common.collect.BiMap; | |||
import com.google.common.collect.HashBiMap; | |||
import com.google.common.collect.Maps; | |||
import org.sonar.api.rules.Rule; | |||
import org.sonar.api.rules.RuleQuery; | |||
import org.sonar.jpa.session.DatabaseSessionFactory; | |||
import java.util.Map; | |||
@@ -39,36 +40,32 @@ public final class CacheRuleFinder extends DefaultRuleFinder { | |||
@Override | |||
public Rule findById(int ruleId) { | |||
Rule rule = rulesById.get(ruleId); | |||
if (rule==null) { | |||
if (rule == null) { | |||
rule = doFindById(ruleId); | |||
addToCache(rule); | |||
if (rule != null) { | |||
loadRepository(rule.getRepositoryKey()); | |||
} | |||
} | |||
return rule; | |||
} | |||
@Override | |||
public Rule findByKey(String repositoryKey, String key) { | |||
Map<String,Rule> repoRules = rulesByKey.get(repositoryKey); | |||
Rule rule = null; | |||
if (repoRules!=null) { | |||
rule = repoRules.get(key); | |||
} | |||
if (rule == null) { | |||
rule = doFindByKey(repositoryKey, key); | |||
addToCache(rule); | |||
} | |||
return rule; | |||
public Rule findByKey(String repositoryKey, String ruleKey) { | |||
Map<String, Rule> repository = loadRepository(repositoryKey); | |||
return repository.get(ruleKey); | |||
} | |||
private void addToCache(Rule rule) { | |||
if (rule != null) { | |||
rulesById.put(rule.getId(), rule); | |||
Map<String, Rule> repoRules = rulesByKey.get(rule.getKey()); | |||
if (repoRules==null) { | |||
repoRules = Maps.newHashMap(); | |||
rulesByKey.put(rule.getRepositoryKey(), repoRules); | |||
private Map<String, Rule> loadRepository(String repositoryKey) { | |||
Map<String, Rule> repository = rulesByKey.get(repositoryKey); | |||
if (repository == null) { | |||
repository = Maps.newHashMap(); | |||
rulesByKey.put(repositoryKey, repository); | |||
for (Rule rule : findAll(RuleQuery.create().withRepositoryKey(repositoryKey))) { | |||
repository.put(rule.getKey(), rule); | |||
rulesById.put(rule.getId(), rule); | |||
} | |||
repoRules.put(rule.getKey(), rule); | |||
} | |||
return repository; | |||
} | |||
} |
@@ -28,7 +28,7 @@ import org.sonar.api.platform.PluginMetadata; | |||
import java.io.File; | |||
import java.util.List; | |||
public final class DefaultPluginMetadata implements PluginMetadata, Comparable<PluginMetadata> { | |||
public class DefaultPluginMetadata implements PluginMetadata, Comparable<PluginMetadata> { | |||
private File file; | |||
private List<File> deployedFiles = Lists.newArrayList(); | |||
private List<File> deprecatedExtensions = Lists.newArrayList(); |
@@ -39,6 +39,10 @@ public class Derby implements Dialect { | |||
return "derby"; | |||
} | |||
public String getActiveRecordJdbcAdapter() { | |||
return "jdbc"; | |||
} | |||
public Class<? extends org.hibernate.dialect.Dialect> getHibernateDialectClass() { | |||
return DerbyWithDecimalDialect.class; | |||
} |
@@ -39,6 +39,11 @@ public interface Dialect { | |||
*/ | |||
String getActiveRecordDialectCode(); | |||
/** | |||
* @return the activerecord-jdbc adapter. See the property 'adapter' in database.yml | |||
*/ | |||
String getActiveRecordJdbcAdapter(); | |||
/** | |||
* Used to autodetect a dialect for a given driver URL | |||
* |
@@ -35,6 +35,10 @@ public class HsqlDb implements Dialect { | |||
return "hsqldb"; | |||
} | |||
public String getActiveRecordJdbcAdapter() { | |||
return "jdbc"; | |||
} | |||
public Class<? extends org.hibernate.dialect.Dialect> getHibernateDialectClass() { | |||
return HSQLDialect.class; | |||
} |
@@ -36,6 +36,10 @@ public class MsSql implements Dialect { | |||
return "sqlserver"; | |||
} | |||
public String getActiveRecordJdbcAdapter() { | |||
return "jdbc"; | |||
} | |||
public Class<? extends org.hibernate.dialect.Dialect> getHibernateDialectClass() { | |||
return MsSqlDialect.class; | |||
} |
@@ -38,6 +38,10 @@ public class MySql implements Dialect { | |||
return "mysql"; | |||
} | |||
public String getActiveRecordJdbcAdapter() { | |||
return "jdbc"; | |||
} | |||
public Class<? extends org.hibernate.dialect.Dialect> getHibernateDialectClass() { | |||
return MySqlWithDecimalDialect.class; | |||
} |
@@ -38,6 +38,10 @@ public class Oracle implements Dialect { | |||
return "oracle"; | |||
} | |||
public String getActiveRecordJdbcAdapter() { | |||
return "oracle_enhanced"; | |||
} | |||
public Class<? extends org.hibernate.dialect.Dialect> getHibernateDialectClass() { | |||
return Oracle10gWithDecimalDialect.class; | |||
} |
@@ -37,6 +37,10 @@ public class PostgreSql implements Dialect { | |||
return "postgre"; | |||
} | |||
public String getActiveRecordJdbcAdapter() { | |||
return "jdbc"; | |||
} | |||
public Class<? extends org.hibernate.dialect.Dialect> getHibernateDialectClass() { | |||
return PostgreSQLWithDecimalDialect.class; | |||
} |
@@ -0,0 +1,100 @@ | |||
/* | |||
* 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.jpa.entity; | |||
import javax.persistence.Column; | |||
import javax.persistence.Entity; | |||
import javax.persistence.GeneratedValue; | |||
import javax.persistence.Id; | |||
import javax.persistence.Table; | |||
/** | |||
* @since 2.11 | |||
*/ | |||
@Entity | |||
@Table(name = "duplications_index") | |||
public class DuplicationBlock { | |||
public static final int BLOCK_HASH_SIZE = 50; | |||
@Id | |||
@Column(name = "id") | |||
@GeneratedValue | |||
private Integer id; | |||
@Column(name = "snapshot_id", updatable = false, nullable = false) | |||
private Integer snapshotId; | |||
@Column(name = "project_snapshot_id", updatable = false, nullable = false) | |||
private Integer projectSnapshotId; | |||
@Column(name = "hash", updatable = false, nullable = false, length = BLOCK_HASH_SIZE) | |||
private String hash; | |||
@Column(name = "index_in_file", updatable = false, nullable = false) | |||
private Integer indexInFile; | |||
@Column(name = "start_line", updatable = false, nullable = false) | |||
private Integer startLine; | |||
@Column(name = "end_line", updatable = false, nullable = false) | |||
private Integer endLine; | |||
public DuplicationBlock() { | |||
} | |||
public DuplicationBlock(Integer projectSnapshotId, Integer snapshotId, String hash, Integer indexInFile, Integer startLine, Integer endLine) { | |||
this.projectSnapshotId = projectSnapshotId; | |||
this.snapshotId = snapshotId; | |||
this.hash = hash; | |||
this.indexInFile = indexInFile; | |||
this.startLine = startLine; | |||
this.endLine = endLine; | |||
} | |||
public Integer getId() { | |||
return id; | |||
} | |||
public Integer getSnapshotId() { | |||
return snapshotId; | |||
} | |||
public Integer getProjectSnapshotId() { | |||
return projectSnapshotId; | |||
} | |||
public String getHash() { | |||
return hash; | |||
} | |||
public Integer getIndexInFile() { | |||
return indexInFile; | |||
} | |||
public Integer getStartLine() { | |||
return startLine; | |||
} | |||
public Integer getEndLine() { | |||
return endLine; | |||
} | |||
} |
@@ -19,6 +19,9 @@ | |||
*/ | |||
package org.sonar.jpa.entity; | |||
import org.apache.commons.lang.builder.ReflectionToStringBuilder; | |||
import org.apache.commons.lang.builder.ToStringStyle; | |||
import javax.persistence.*; | |||
import java.util.Date; | |||
@@ -91,4 +94,9 @@ public final class ManualMeasure { | |||
public String getUserLogin() { | |||
return userLogin; | |||
} | |||
@Override | |||
public String toString() { | |||
return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString(); | |||
} | |||
} |
@@ -20,6 +20,8 @@ | |||
package org.sonar.jpa.entity; | |||
import org.apache.commons.io.IOUtils; | |||
import org.apache.commons.lang.builder.ReflectionToStringBuilder; | |||
import org.apache.commons.lang.builder.ToStringStyle; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.utils.SonarException; | |||
@@ -98,4 +100,8 @@ public class NotificationQueueElement { | |||
} | |||
} | |||
@Override | |||
public String toString() { | |||
return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString(); | |||
} | |||
} |
@@ -19,15 +19,17 @@ | |||
*/ | |||
package org.sonar.jpa.entity; | |||
import org.apache.commons.lang.builder.ReflectionToStringBuilder; | |||
import org.apache.commons.lang.builder.ToStringStyle; | |||
import javax.persistence.*; | |||
import java.sql.Connection; | |||
import java.sql.ResultSet; | |||
import java.sql.SQLException; | |||
import java.sql.Statement; | |||
import javax.persistence.*; | |||
@Entity | |||
@Table(name = SchemaMigration.TABLE_NAME, uniqueConstraints = { @UniqueConstraint(columnNames = { "version" }) }) | |||
@Table(name = SchemaMigration.TABLE_NAME, uniqueConstraints = {@UniqueConstraint(columnNames = {"version"})}) | |||
public class SchemaMigration { | |||
public final static int VERSION_UNKNOWN = -1; | |||
@@ -40,7 +42,7 @@ public class SchemaMigration { | |||
- complete the Derby DDL file used for unit tests : sonar-testing-harness/src/main/resources/org/sonar/test/persistence/sonar-test.ddl | |||
*/ | |||
public static final int LAST_VERSION = 216; | |||
public static final int LAST_VERSION = 217; | |||
public final static String TABLE_NAME = "schema_migrations"; | |||
@@ -102,4 +104,9 @@ public class SchemaMigration { | |||
} | |||
} | |||
} | |||
@Override | |||
public String toString() { | |||
return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString(); | |||
} | |||
} |
@@ -36,7 +36,8 @@ | |||
<class>org.sonar.api.rules.ActiveRuleParamChange</class> | |||
<class>org.sonar.jpa.entity.Review</class> | |||
<class>org.sonar.jpa.entity.NotificationQueueElement</class> | |||
<class>org.sonar.jpa.entity.DuplicationBlock</class> | |||
<properties> | |||
<property name="hibernate.current_session_context_class" value="thread"/> | |||
<property name="hibernate.connection.release_mode" value="after_transaction"/> |
@@ -24,6 +24,7 @@ import org.sonar.api.rules.Rule; | |||
import org.sonar.api.rules.RuleFinder; | |||
import org.sonar.jpa.test.AbstractDbUnitTestCase; | |||
import static org.hamcrest.CoreMatchers.nullValue; | |||
import static org.hamcrest.core.Is.is; | |||
import static org.hamcrest.core.IsNull.notNullValue; | |||
import static org.junit.Assert.assertThat; | |||
@@ -41,6 +42,13 @@ public class CacheRuleFinderTest extends AbstractDbUnitTestCase { | |||
assertThat(finder.findById(3), notNullValue()); | |||
} | |||
@Test | |||
public void shouldNotFailIfUnknownRuleId() { | |||
setupData("shared"); | |||
RuleFinder finder = new CacheRuleFinder(getSessionFactory()); | |||
assertThat(finder.findById(33456816), nullValue()); | |||
} | |||
@Test | |||
public void shouldCacheFindByKey() { | |||
setupData("shared"); |
@@ -63,8 +63,12 @@ public class DialectRepositoryTest { | |||
} | |||
public String getActiveRecordDialectCode() { | |||
return null; | |||
return "test"; | |||
} | |||
public String getActiveRecordJdbcAdapter() { | |||
return "jdbc"; | |||
} | |||
} | |||
} |