aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien HENRY <julien.henry@sonarsource.com>2014-01-21 14:55:24 +0100
committerJulien HENRY <julien.henry@sonarsource.com>2014-01-24 16:02:04 +0100
commit08de7bc30c13fdd63d8e4342a57f4b67d7c15aa9 (patch)
tree8bb9933ac0606a2b61c53c49dab639ef032ad3a3
parent547f5d859251a6c4406fcc94db5faa54362465f3 (diff)
downloadsonarqube-08de7bc30c13fdd63d8e4342a57f4b67d7c15aa9.tar.gz
sonarqube-08de7bc30c13fdd63d8e4342a57f4b67d7c15aa9.zip
SONAR-926 Multi-language support:
* RulesProfile wrapper * ModuleFileSystem now support multi language * Sensors executed trying each language until one is found
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java94
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProfileEventsSensor.java60
-rw-r--r--plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProfileSensor.java30
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileEventsSensorTest.java53
-rw-r--r--plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileSensorTest.java21
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java41
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java41
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/DefaultProfileLoader.java47
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/ProfileLoader.java4
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/ProfileProvider.java5
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/RulesProfileWrapper.java150
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java5
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/phases/InitializersExecutor.java4
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java28
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/phases/ProjectInitializer.java31
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/phases/SensorsExecutor.java51
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java14
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java22
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ComponentIndexer.java (renamed from sonar-batch/src/main/java/org/sonar/batch/phases/FileIndexer.java)65
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystem.java37
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileIndex.java24
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/LanguageRecognizer.java117
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/PathPattern.java4
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/language/ModuleLanguages.java71
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/DefaultProfileLoaderTest.java99
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/ComponentIndexerTest.java (renamed from sonar-batch/src/test/java/org/sonar/batch/phases/FileIndexerTest.java)37
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystemTest.java27
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/LanguageRecognizerTest.java168
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java4
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/resources/Languages.java9
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/resources/Project.java10
31 files changed, 1003 insertions, 370 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
index 79df113891e..00009b74c6d 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
@@ -20,10 +20,13 @@
package org.sonar.plugins.core;
import com.google.common.collect.ImmutableList;
-import org.sonar.api.*;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.Properties;
+import org.sonar.api.Property;
+import org.sonar.api.PropertyType;
+import org.sonar.api.SonarPlugin;
import org.sonar.api.checks.NoSonarFilter;
import org.sonar.api.config.PropertyDefinition;
-import org.sonar.api.resources.Java;
import org.sonar.api.resources.Qualifiers;
import org.sonar.batch.components.PastSnapshotFinder;
import org.sonar.core.technicaldebt.TechnicalDebtConverter;
@@ -33,21 +36,91 @@ import org.sonar.plugins.core.charts.DistributionAreaChart;
import org.sonar.plugins.core.charts.DistributionBarChart;
import org.sonar.plugins.core.charts.XradarChart;
import org.sonar.plugins.core.colorizers.JavaColorizerFormat;
-import org.sonar.plugins.core.dashboards.*;
-import org.sonar.plugins.core.issue.*;
+import org.sonar.plugins.core.dashboards.GlobalDefaultDashboard;
+import org.sonar.plugins.core.dashboards.ProjectDefaultDashboard;
+import org.sonar.plugins.core.dashboards.ProjectHotspotDashboard;
+import org.sonar.plugins.core.dashboards.ProjectIssuesDashboard;
+import org.sonar.plugins.core.dashboards.ProjectTimeMachineDashboard;
+import org.sonar.plugins.core.issue.CountFalsePositivesDecorator;
+import org.sonar.plugins.core.issue.CountUnresolvedIssuesDecorator;
+import org.sonar.plugins.core.issue.InitialOpenIssuesSensor;
+import org.sonar.plugins.core.issue.InitialOpenIssuesStack;
+import org.sonar.plugins.core.issue.IssueHandlers;
+import org.sonar.plugins.core.issue.IssueTracking;
+import org.sonar.plugins.core.issue.IssueTrackingDecorator;
+import org.sonar.plugins.core.issue.IssuesDensityDecorator;
+import org.sonar.plugins.core.issue.WeightedIssuesDecorator;
import org.sonar.plugins.core.issue.ignore.IgnoreIssuesPlugin;
-import org.sonar.plugins.core.issue.notification.*;
+import org.sonar.plugins.core.issue.notification.ChangesOnMyIssueNotificationDispatcher;
+import org.sonar.plugins.core.issue.notification.IssueChangesEmailTemplate;
+import org.sonar.plugins.core.issue.notification.NewFalsePositiveNotificationDispatcher;
+import org.sonar.plugins.core.issue.notification.NewIssuesEmailTemplate;
+import org.sonar.plugins.core.issue.notification.NewIssuesNotificationDispatcher;
+import org.sonar.plugins.core.issue.notification.SendIssueNotificationsPostJob;
import org.sonar.plugins.core.measurefilters.MyFavouritesFilter;
import org.sonar.plugins.core.measurefilters.ProjectFilter;
import org.sonar.plugins.core.notifications.alerts.NewAlerts;
import org.sonar.plugins.core.security.ApplyProjectRolesDecorator;
-import org.sonar.plugins.core.sensors.*;
+import org.sonar.plugins.core.sensors.BranchCoverageDecorator;
+import org.sonar.plugins.core.sensors.CheckAlertThresholds;
+import org.sonar.plugins.core.sensors.CommentDensityDecorator;
+import org.sonar.plugins.core.sensors.CoverageDecorator;
+import org.sonar.plugins.core.sensors.CoverageMeasurementFilter;
+import org.sonar.plugins.core.sensors.DirectoriesDecorator;
+import org.sonar.plugins.core.sensors.FileHashSensor;
+import org.sonar.plugins.core.sensors.FilesDecorator;
+import org.sonar.plugins.core.sensors.GenerateAlertEvents;
+import org.sonar.plugins.core.sensors.ItBranchCoverageDecorator;
+import org.sonar.plugins.core.sensors.ItCoverageDecorator;
+import org.sonar.plugins.core.sensors.ItLineCoverageDecorator;
+import org.sonar.plugins.core.sensors.LineCoverageDecorator;
+import org.sonar.plugins.core.sensors.ManualMeasureDecorator;
+import org.sonar.plugins.core.sensors.OverallBranchCoverageDecorator;
+import org.sonar.plugins.core.sensors.OverallCoverageDecorator;
+import org.sonar.plugins.core.sensors.OverallLineCoverageDecorator;
+import org.sonar.plugins.core.sensors.ProfileEventsSensor;
+import org.sonar.plugins.core.sensors.ProfileSensor;
+import org.sonar.plugins.core.sensors.ProjectLinksSensor;
+import org.sonar.plugins.core.sensors.UnitTestDecorator;
+import org.sonar.plugins.core.sensors.VersionEventsSensor;
import org.sonar.plugins.core.technicaldebt.NewTechnicalDebtDecorator;
import org.sonar.plugins.core.technicaldebt.TechnicalDebtDecorator;
-import org.sonar.plugins.core.timemachine.*;
+import org.sonar.plugins.core.timemachine.NewCoverageAggregator;
+import org.sonar.plugins.core.timemachine.NewCoverageFileAnalyzer;
+import org.sonar.plugins.core.timemachine.NewItCoverageFileAnalyzer;
+import org.sonar.plugins.core.timemachine.NewOverallCoverageFileAnalyzer;
+import org.sonar.plugins.core.timemachine.TendencyDecorator;
+import org.sonar.plugins.core.timemachine.TimeMachineConfigurationPersister;
+import org.sonar.plugins.core.timemachine.VariationDecorator;
import org.sonar.plugins.core.web.TestsViewer;
-import org.sonar.plugins.core.widgets.*;
-import org.sonar.plugins.core.widgets.issues.*;
+import org.sonar.plugins.core.widgets.AlertsWidget;
+import org.sonar.plugins.core.widgets.BubbleChartWidget;
+import org.sonar.plugins.core.widgets.ComplexityWidget;
+import org.sonar.plugins.core.widgets.CoverageWidget;
+import org.sonar.plugins.core.widgets.CustomMeasuresWidget;
+import org.sonar.plugins.core.widgets.DescriptionWidget;
+import org.sonar.plugins.core.widgets.DocumentationCommentsWidget;
+import org.sonar.plugins.core.widgets.DuplicationsWidget;
+import org.sonar.plugins.core.widgets.EventsWidget;
+import org.sonar.plugins.core.widgets.HotspotMetricWidget;
+import org.sonar.plugins.core.widgets.HotspotMostViolatedResourcesWidget;
+import org.sonar.plugins.core.widgets.HotspotMostViolatedRulesWidget;
+import org.sonar.plugins.core.widgets.ItCoverageWidget;
+import org.sonar.plugins.core.widgets.MeasureFilterListWidget;
+import org.sonar.plugins.core.widgets.MeasureFilterTreemapWidget;
+import org.sonar.plugins.core.widgets.SizeWidget;
+import org.sonar.plugins.core.widgets.TechnicalDebtPyramidWidget;
+import org.sonar.plugins.core.widgets.TimeMachineWidget;
+import org.sonar.plugins.core.widgets.TimelineWidget;
+import org.sonar.plugins.core.widgets.TreemapWidget;
+import org.sonar.plugins.core.widgets.WelcomeWidget;
+import org.sonar.plugins.core.widgets.issues.ActionPlansWidget;
+import org.sonar.plugins.core.widgets.issues.FalsePositiveIssuesWidget;
+import org.sonar.plugins.core.widgets.issues.IssueFilterWidget;
+import org.sonar.plugins.core.widgets.issues.IssuesWidget;
+import org.sonar.plugins.core.widgets.issues.MyUnresolvedIssuesWidget;
+import org.sonar.plugins.core.widgets.issues.UnresolvedIssuesPerAssigneeWidget;
+import org.sonar.plugins.core.widgets.issues.UnresolvedIssuesStatusesWidget;
import org.sonar.plugins.core.widgets.measures.MeasureFilterAsBubbleChartWidget;
import org.sonar.plugins.core.widgets.measures.MeasureFilterAsHistogramWidget;
import org.sonar.plugins.core.widgets.measures.MeasureFilterAsPieChartWidget;
@@ -113,9 +186,8 @@ import java.util.List;
category = CoreProperties.CATEGORY_GENERAL),
@Property(
key = CoreProperties.PROJECT_LANGUAGE_PROPERTY,
- defaultValue = Java.KEY,
name = "Default language",
- description = "Default language of the source code to analyse",
+ description = "[Deprecated] Default language of the source code to analyse. Keep it blank to allow multi-language analysis.",
project = false,
global = true,
category = CoreProperties.CATEGORY_GENERAL),
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProfileEventsSensor.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProfileEventsSensor.java
index 14db37632f3..39e722c099e 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProfileEventsSensor.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProfileEventsSensor.java
@@ -29,6 +29,8 @@ import org.sonar.api.measures.Measure;
import org.sonar.api.measures.Metric;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.Project;
+import org.sonar.batch.RulesProfileWrapper;
+import org.sonar.batch.scan.language.ModuleLanguages;
import java.util.List;
@@ -36,41 +38,47 @@ public class ProfileEventsSensor implements Sensor {
private final RulesProfile profile;
private final TimeMachine timeMachine;
+ private final ModuleLanguages moduleLanguages;
- public ProfileEventsSensor(RulesProfile profile, TimeMachine timeMachine) {
+ public ProfileEventsSensor(RulesProfile profile, TimeMachine timeMachine, ModuleLanguages moduleLanguages) {
this.profile = profile;
this.timeMachine = timeMachine;
+ this.moduleLanguages = moduleLanguages;
}
public boolean shouldExecuteOnProject(Project project) {
- // Views will define a fake profile with a null id
- return profile.getId() != null;
+ // Views will define a fake profile
+ return profile instanceof RulesProfileWrapper;
}
public void analyse(Project project, SensorContext context) {
- Measure pastProfileMeasure = getPreviousMeasure(project, CoreMetrics.PROFILE);
- if (pastProfileMeasure == null) {
- // first analysis
- return;
- }
- int pastProfileId = pastProfileMeasure.getIntValue();
- Measure pastProfileVersionMeasure = getPreviousMeasure(project, CoreMetrics.PROFILE_VERSION);
- final int pastProfileVersion;
- // first analysis with versions
- if (pastProfileVersionMeasure == null) {
- pastProfileVersion = 1;
- } else {
- pastProfileVersion = pastProfileVersionMeasure.getIntValue();
- }
- String pastProfile = formatProfileDescription(pastProfileMeasure.getData(), pastProfileVersion);
+ RulesProfileWrapper profileWrapper = (RulesProfileWrapper) profile;
+ for (String languageKey : moduleLanguages.getModuleLanguageKeys()) {
+ RulesProfile realProfile = profileWrapper.getProfileByLanguage(languageKey);
+ Measure pastProfileMeasure = getPreviousMeasure(project, CoreMetrics.PROFILE);
+ if (pastProfileMeasure == null) {
+ // first analysis
+ return;
+ }
+ int pastProfileId = pastProfileMeasure.getIntValue();
+ Measure pastProfileVersionMeasure = getPreviousMeasure(project, CoreMetrics.PROFILE_VERSION);
+ final int pastProfileVersion;
+ // first analysis with versions
+ if (pastProfileVersionMeasure == null) {
+ pastProfileVersion = 1;
+ } else {
+ pastProfileVersion = pastProfileVersionMeasure.getIntValue();
+ }
+ String pastProfile = formatProfileDescription(pastProfileMeasure.getData(), pastProfileVersion);
- int currentProfileId = profile.getId();
- int currentProfileVersion = profile.getVersion();
- String currentProfile = formatProfileDescription(profile.getName(), currentProfileVersion);
+ int currentProfileId = realProfile.getId();
+ int currentProfileVersion = realProfile.getVersion();
+ String currentProfile = formatProfileDescription(realProfile.getName(), currentProfileVersion);
- if ((pastProfileId != currentProfileId) || (pastProfileVersion != currentProfileVersion)) {
- // A different profile is used for this project or new version of same profile
- context.createEvent(project, currentProfile, currentProfile + " is used instead of " + pastProfile, Event.CATEGORY_PROFILE, null);
+ if ((pastProfileId != currentProfileId) || (pastProfileVersion != currentProfileVersion)) {
+ // A different profile is used for this project or new version of same profile
+ context.createEvent(project, currentProfile, currentProfile + " is used instead of " + pastProfile, Event.CATEGORY_PROFILE, null);
+ }
}
}
@@ -80,8 +88,8 @@ public class ProfileEventsSensor implements Sensor {
private Measure getPreviousMeasure(Project project, Metric metric) {
TimeMachineQuery query = new TimeMachineQuery(project)
- .setOnlyLastAnalysis(true)
- .setMetrics(metric);
+ .setOnlyLastAnalysis(true)
+ .setMetrics(metric);
List<Measure> measures = timeMachine.getMeasures(query);
if (measures.isEmpty()) {
return null;
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProfileSensor.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProfileSensor.java
index d59b9ded823..ffb6252101c 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProfileSensor.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/ProfileSensor.java
@@ -26,33 +26,41 @@ import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Measure;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.Project;
+import org.sonar.batch.RulesProfileWrapper;
+import org.sonar.batch.scan.language.ModuleLanguages;
public class ProfileSensor implements Sensor {
private final RulesProfile profile;
private final DatabaseSession session;
+ private ModuleLanguages languages;
- public ProfileSensor(RulesProfile profile, DatabaseSession session) {
+ public ProfileSensor(RulesProfile profile, DatabaseSession session, ModuleLanguages languages) {
this.profile = profile;
this.session = session;
+ this.languages = languages;
}
public boolean shouldExecuteOnProject(Project project) {
- // Views will define a fake profile with a null id
- return profile.getId() != null;
+ // Views will define a fake profile
+ return profile instanceof RulesProfileWrapper;
}
public void analyse(Project project, SensorContext context) {
- Measure measure = new Measure(CoreMetrics.PROFILE, profile.getName());
- Measure measureVersion = new Measure(CoreMetrics.PROFILE_VERSION, Integer.valueOf(profile.getVersion()).doubleValue());
- if (profile.getId() != null) {
- measure.setValue(profile.getId().doubleValue());
+ RulesProfileWrapper wrapper = (RulesProfileWrapper) profile;
+ for (String languageKey : languages.getModuleLanguageKeys()) {
+ RulesProfile realProfile = wrapper.getProfileByLanguage(languageKey);
+ Measure measure = new Measure(CoreMetrics.PROFILE, profile.getName());
+ Measure measureVersion = new Measure(CoreMetrics.PROFILE_VERSION, Integer.valueOf(profile.getVersion()).doubleValue());
+ if (realProfile.getId() != null) {
+ measure.setValue(realProfile.getId().doubleValue());
- profile.setUsed(true);
- session.merge(profile);
+ realProfile.setUsed(true);
+ session.merge(realProfile);
+ }
+ context.saveMeasure(measure);
+ context.saveMeasure(measureVersion);
}
- context.saveMeasure(measure);
- context.saveMeasure(measureVersion);
}
@Override
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileEventsSensorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileEventsSensorTest.java
index f7a44372623..84d586a5874 100644
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileEventsSensorTest.java
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileEventsSensorTest.java
@@ -25,47 +25,66 @@ import org.sonar.api.batch.Event;
import org.sonar.api.batch.SensorContext;
import org.sonar.api.batch.TimeMachine;
import org.sonar.api.batch.TimeMachineQuery;
+import org.sonar.api.config.Settings;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Measure;
import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.resources.Java;
+import org.sonar.api.resources.Languages;
import org.sonar.api.resources.Project;
+import org.sonar.batch.RulesProfileWrapper;
+import org.sonar.batch.scan.language.ModuleLanguages;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.same;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
public class ProfileEventsSensorTest {
private Project project;
private SensorContext context;
+ private ModuleLanguages moduleLanguages;
+ private RulesProfileWrapper wrapper;
+ private RulesProfile profile;
@Before
public void prepare() {
project = mock(Project.class);
context = mock(SensorContext.class);
+
+ moduleLanguages = new ModuleLanguages(new Settings(), new Languages(Java.INSTANCE));
+ moduleLanguages.addLanguage("java");
+ Map<String, RulesProfile> ruleProfilesPerLanguages = new HashMap<String, RulesProfile>();
+ profile = mock(RulesProfile.class);
+ ruleProfilesPerLanguages.put("java", profile);
+ wrapper = new RulesProfileWrapper(moduleLanguages, ruleProfilesPerLanguages);
}
@Test
public void shouldExecuteWhenProfileWithId() {
- RulesProfile profile = mock(RulesProfile.class);
when(profile.getId()).thenReturn(123);
- ProfileEventsSensor sensor = new ProfileEventsSensor(profile, null);
+ ProfileEventsSensor sensor = new ProfileEventsSensor(wrapper, null, moduleLanguages);
assertThat(sensor.shouldExecuteOnProject(project)).isTrue();
verifyZeroInteractions(project);
}
@Test
- public void shouldNotExecuteIfProfileWithoutId() {
+ public void shouldNotExecuteIfProfileIsNotWrapper() {
RulesProfile profile = mock(RulesProfile.class);
when(profile.getId()).thenReturn(null);
- ProfileEventsSensor sensor = new ProfileEventsSensor(profile, null);
+ ProfileEventsSensor sensor = new ProfileEventsSensor(profile, null, moduleLanguages);
assertThat(sensor.shouldExecuteOnProject(project)).isFalse();
verifyZeroInteractions(project);
@@ -73,9 +92,9 @@ public class ProfileEventsSensorTest {
@Test
public void shouldDoNothingIfNoProfileChange() {
- RulesProfile profile = mockProfileWithVersion(1);
+ mockProfileWithVersion(1);
TimeMachine timeMachine = mockTM(22.0, "Foo", 1.0); // Same profile, same version
- ProfileEventsSensor sensor = new ProfileEventsSensor(profile, timeMachine);
+ ProfileEventsSensor sensor = new ProfileEventsSensor(wrapper, timeMachine, moduleLanguages);
sensor.analyse(project, context);
@@ -84,9 +103,9 @@ public class ProfileEventsSensorTest {
@Test
public void shouldCreateEventIfProfileChange() {
- RulesProfile profile = mockProfileWithVersion(1);
+ mockProfileWithVersion(1);
TimeMachine timeMachine = mockTM(21.0, "Bar", 1.0); // Different profile
- ProfileEventsSensor sensor = new ProfileEventsSensor(profile, timeMachine);
+ ProfileEventsSensor sensor = new ProfileEventsSensor(wrapper, timeMachine, moduleLanguages);
sensor.analyse(project, context);
@@ -98,9 +117,9 @@ public class ProfileEventsSensorTest {
@Test
public void shouldCreateEventIfProfileVersionChange() {
- RulesProfile profile = mockProfileWithVersion(2);
+ mockProfileWithVersion(2);
TimeMachine timeMachine = mockTM(22.0, "Foo", 1.0); // Same profile, different version
- ProfileEventsSensor sensor = new ProfileEventsSensor(profile, timeMachine);
+ ProfileEventsSensor sensor = new ProfileEventsSensor(wrapper, timeMachine, moduleLanguages);
sensor.analyse(project, context);
@@ -112,9 +131,9 @@ public class ProfileEventsSensorTest {
@Test
public void shouldNotCreateEventIfFirstAnalysis() {
- RulesProfile profile = mockProfileWithVersion(2);
+ mockProfileWithVersion(2);
TimeMachine timeMachine = mockTM(null, null);
- ProfileEventsSensor sensor = new ProfileEventsSensor(profile, timeMachine);
+ ProfileEventsSensor sensor = new ProfileEventsSensor(wrapper, timeMachine, moduleLanguages);
sensor.analyse(project, context);
@@ -123,9 +142,9 @@ public class ProfileEventsSensorTest {
@Test
public void shouldCreateEventIfFirstAnalysisWithVersionsAndVersionMoreThan1() {
- RulesProfile profile = mockProfileWithVersion(2);
+ mockProfileWithVersion(2);
TimeMachine timeMachine = mockTM(22.0, "Foo", null);
- ProfileEventsSensor sensor = new ProfileEventsSensor(profile, timeMachine);
+ ProfileEventsSensor sensor = new ProfileEventsSensor(wrapper, timeMachine, moduleLanguages);
sensor.analyse(project, context);
@@ -135,12 +154,10 @@ public class ProfileEventsSensorTest {
same(Event.CATEGORY_PROFILE), any(Date.class));
}
- private RulesProfile mockProfileWithVersion(int version) {
- RulesProfile profile = mock(RulesProfile.class);
+ private void mockProfileWithVersion(int version) {
when(profile.getId()).thenReturn(22);
when(profile.getName()).thenReturn("Foo");
when(profile.getVersion()).thenReturn(version);
- return profile;
}
private TimeMachine mockTM(double profileId, String profileName, Double versionValue) {
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileSensorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileSensorTest.java
index cce7bab1087..4aa3ae589e5 100644
--- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileSensorTest.java
+++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/ProfileSensorTest.java
@@ -21,13 +21,23 @@ package org.sonar.plugins.core.sensors;
import org.junit.Test;
import org.sonar.api.batch.SensorContext;
+import org.sonar.api.config.Settings;
import org.sonar.api.database.DatabaseSession;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.resources.Java;
+import org.sonar.api.resources.Languages;
import org.sonar.api.test.IsMeasure;
+import org.sonar.batch.RulesProfileWrapper;
+import org.sonar.batch.scan.language.ModuleLanguages;
+
+import java.util.HashMap;
+import java.util.Map;
import static org.mockito.Matchers.argThat;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
public class ProfileSensorTest {
@@ -37,10 +47,17 @@ public class ProfileSensorTest {
when(profile.getId()).thenReturn(22);
when(profile.getName()).thenReturn("fake");
when(profile.getVersion()).thenReturn(2);
+
+ ModuleLanguages moduleLanguages = new ModuleLanguages(new Settings(), new Languages(Java.INSTANCE));
+ moduleLanguages.addLanguage("java");
+ Map<String, RulesProfile> ruleProfilesPerLanguages = new HashMap<String, RulesProfile>();
+ ruleProfilesPerLanguages.put("java", profile);
+ RulesProfileWrapper wrapper = new RulesProfileWrapper(moduleLanguages, ruleProfilesPerLanguages);
+
SensorContext context = mock(SensorContext.class);
DatabaseSession session = mock(DatabaseSession.class);
- ProfileSensor sensor = new ProfileSensor(profile, session);
+ ProfileSensor sensor = new ProfileSensor(wrapper, session, moduleLanguages);
sensor.analyse(null, context);
verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.PROFILE, 22d)));
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java
index d83bb5428d4..47c3c15e55d 100644
--- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java
@@ -26,7 +26,9 @@ import org.sonar.api.CoreProperties;
import org.sonar.api.batch.Sensor;
import org.sonar.api.batch.SensorContext;
import org.sonar.api.config.Settings;
+import org.sonar.api.resources.Language;
import org.sonar.api.resources.Project;
+import org.sonar.batch.scan.language.ModuleLanguages;
public class CpdSensor implements Sensor {
@@ -35,30 +37,22 @@ public class CpdSensor implements Sensor {
private CpdEngine sonarEngine;
private CpdEngine sonarBridgeEngine;
private Settings settings;
+ private ModuleLanguages moduleLanguages;
- public CpdSensor(SonarEngine sonarEngine, SonarBridgeEngine sonarBridgeEngine, Settings settings) {
+ public CpdSensor(SonarEngine sonarEngine, SonarBridgeEngine sonarBridgeEngine, Settings settings, ModuleLanguages moduleLanguages) {
this.sonarEngine = sonarEngine;
this.sonarBridgeEngine = sonarBridgeEngine;
this.settings = settings;
+ this.moduleLanguages = moduleLanguages;
}
public boolean shouldExecuteOnProject(Project project) {
- if (isSkipped(project)) {
- LOG.info("Detection of duplicated code is skipped");
- return false;
- }
-
- if (!getEngine(project).isLanguageSupported(project.getLanguage())) {
- LOG.debug("Detection of duplicated code is not supported for {}.", project.getLanguage());
- return false;
- }
-
return true;
}
@VisibleForTesting
- CpdEngine getEngine(Project project) {
- if (sonarEngine.isLanguageSupported(project.getLanguage())) {
+ CpdEngine getEngine(Language language) {
+ if (sonarEngine.isLanguageSupported(language)) {
return sonarEngine;
} else {
return sonarBridgeEngine;
@@ -66,8 +60,8 @@ public class CpdSensor implements Sensor {
}
@VisibleForTesting
- boolean isSkipped(Project project) {
- String key = "sonar.cpd." + project.getLanguageKey() + ".skip";
+ boolean isSkipped(Language language) {
+ String key = "sonar.cpd." + language.getKey() + ".skip";
if (settings.hasKey(key)) {
return settings.getBoolean(key);
}
@@ -75,9 +69,20 @@ public class CpdSensor implements Sensor {
}
public void analyse(Project project, SensorContext context) {
- CpdEngine engine = getEngine(project);
- LOG.info("{} is used", engine);
- engine.analyse(project, context);
+ for (Language language : moduleLanguages.getModuleLanguages()) {
+ if (isSkipped(language)) {
+ LOG.info("Detection of duplicated code is skipped for {}.", language);
+ continue;
+ }
+
+ CpdEngine engine = getEngine(language);
+ if (!engine.isLanguageSupported(language)) {
+ LOG.debug("Detection of duplicated code is not supported for {}.", language);
+ continue;
+ }
+ LOG.info("{} is used for {}", engine, language);
+ engine.analyse(project, context);
+ }
}
@Override
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java
index 0cf3ade848b..d410d658eeb 100644
--- a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java
+++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java
@@ -19,14 +19,15 @@
*/
package org.sonar.plugins.cpd;
-import org.apache.commons.configuration.PropertiesConfiguration;
import org.junit.Before;
import org.junit.Test;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.Settings;
+import org.sonar.api.resources.AbstractLanguage;
import org.sonar.api.resources.Java;
import org.sonar.api.resources.Language;
-import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Languages;
+import org.sonar.batch.scan.language.ModuleLanguages;
import org.sonar.plugins.cpd.index.IndexFactory;
import static org.fest.assertions.Assertions.assertThat;
@@ -38,6 +39,7 @@ public class CpdSensorTest {
SonarBridgeEngine sonarBridgeEngine;
CpdSensor sensor;
Settings settings;
+ private Language phpLanguage;
@Before
public void setUp() {
@@ -45,45 +47,40 @@ public class CpdSensorTest {
sonarEngine = new SonarEngine(indexFactory, null, null);
sonarBridgeEngine = new SonarBridgeEngine(indexFactory, null, null);
settings = new Settings(new PropertyDefinitions(CpdPlugin.class));
- sensor = new CpdSensor(sonarEngine, sonarBridgeEngine, settings);
+ phpLanguage = new AbstractLanguage("php", "PHP") {
+
+ @Override
+ public String[] getFileSuffixes() {
+ return null;
+ }
+ };
+ sensor = new CpdSensor(sonarEngine, sonarBridgeEngine, settings, new ModuleLanguages(settings, new Languages()));
}
@Test
public void test_global_skip() {
settings.setProperty("sonar.cpd.skip", true);
- assertThat(sensor.isSkipped(createJavaProject())).isTrue();
+ assertThat(sensor.isSkipped(Java.INSTANCE)).isTrue();
}
@Test
public void should_not_skip_by_default() {
- assertThat(sensor.isSkipped(createJavaProject())).isFalse();
+ assertThat(sensor.isSkipped(Java.INSTANCE)).isFalse();
}
@Test
public void should_skip_by_language() {
settings.setProperty("sonar.cpd.skip", false);
settings.setProperty("sonar.cpd.php.skip", true);
- assertThat(sensor.isSkipped(createPhpProject())).isTrue();
- assertThat(sensor.isSkipped(createJavaProject())).isFalse();
+
+ assertThat(sensor.isSkipped(phpLanguage)).isTrue();
+ assertThat(sensor.isSkipped(Java.INSTANCE)).isFalse();
}
@Test
public void test_engine() {
- assertThat(sensor.getEngine(createJavaProject())).isSameAs(sonarEngine);
- assertThat(sensor.getEngine(createPhpProject())).isSameAs(sonarBridgeEngine);
- }
-
- private Project createJavaProject() {
- PropertiesConfiguration conf = new PropertiesConfiguration();
- conf.setProperty("sonar.language", "java");
- return new Project("java_project").setConfiguration(conf).setLanguage(Java.INSTANCE);
- }
-
- private Project createPhpProject() {
- PropertiesConfiguration conf = new PropertiesConfiguration();
- conf.setProperty("sonar.language", "php");
- Language phpLanguage = mock(Language.class);
- return new Project("php_project").setConfiguration(conf).setLanguage(phpLanguage);
+ assertThat(sensor.getEngine(Java.INSTANCE)).isSameAs(sonarEngine);
+ assertThat(sensor.getEngine(phpLanguage)).isSameAs(sonarBridgeEngine);
}
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/DefaultProfileLoader.java b/sonar-batch/src/main/java/org/sonar/batch/DefaultProfileLoader.java
index 8e8e02aa5c1..797723590d0 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/DefaultProfileLoader.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/DefaultProfileLoader.java
@@ -24,45 +24,56 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.config.Settings;
import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
import org.sonar.api.resources.Project;
import org.sonar.api.rules.ActiveRule;
import org.sonar.api.utils.SonarException;
+import org.sonar.batch.scan.language.ModuleLanguages;
import org.sonar.jpa.dao.ProfilesDao;
+import java.util.HashMap;
+import java.util.Map;
+
public class DefaultProfileLoader implements ProfileLoader {
private static final Logger LOG = LoggerFactory.getLogger(DefaultProfileLoader.class);
private ProfilesDao dao;
+ private Languages languages;
+ private ModuleLanguages moduleLanguages;
- public DefaultProfileLoader(ProfilesDao dao) {
+ public DefaultProfileLoader(ProfilesDao dao, ModuleLanguages moduleLanguages, Languages languages) {
this.dao = dao;
+ this.moduleLanguages = moduleLanguages;
+ this.languages = languages;
}
- public RulesProfile load(Project project, Settings settings) {
+ public RulesProfile load(Project module, Settings settings) {
+ Map<String, RulesProfile> profilesPerLanguageKey = new HashMap<String, RulesProfile>();
+
+ // TODO For now we have to load profile of all languages even if module will only use part of them because ModuleLanguages may not be
+ // initialized yet
+ for (Language language : languages.all()) {
+ String languageKey = language.getKey();
- String profileName = StringUtils.defaultIfBlank(
+ String profileName = StringUtils.defaultIfBlank(
settings.getString("sonar.profile"),
- settings.getString("sonar.profile." + project.getLanguageKey()));
+ settings.getString("sonar.profile." + languageKey));
- if (StringUtils.isBlank(profileName)) {
- // This means that the current language is not supported by any installed plugin, otherwise at least a
- // "Default <Language Name>" profile would have been created by ActivateDefaultProfiles class.
- String msg = "You must install a plugin that supports the language key '" + project.getLanguageKey() + "'.";
- if (!LOG.isDebugEnabled()) {
- msg += " Run analysis in verbose mode to see list of available language keys.";
- } else {
- msg += " See analysis log for a list of available language keys.";
+ RulesProfile profile = dao.getProfile(languageKey, profileName);
+ if (profile == null) {
+ throw new SonarException("Quality profile not found : " + profileName + ", language " + languageKey);
}
- throw new SonarException(msg);
- }
- RulesProfile profile = dao.getProfile(project.getLanguageKey(), profileName);
- if (profile == null) {
- throw new SonarException("Quality profile not found : " + profileName + ", language " + project.getLanguageKey());
+ profilesPerLanguageKey.put(languageKey, hibernateHack(profile));
}
- return hibernateHack(profile);
+ RulesProfile profile = new RulesProfileWrapper(moduleLanguages, profilesPerLanguageKey);
+ for (Map.Entry<String, RulesProfile> profiles : profilesPerLanguageKey.entrySet()) {
+ LOG.info("Quality profile for {}: {}", profiles.getKey(), profiles.getValue());
+ }
+ return profile;
}
private RulesProfile hibernateHack(RulesProfile profile) {
diff --git a/sonar-batch/src/main/java/org/sonar/batch/ProfileLoader.java b/sonar-batch/src/main/java/org/sonar/batch/ProfileLoader.java
index c844cef918f..51ce91c7b37 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/ProfileLoader.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/ProfileLoader.java
@@ -23,6 +23,10 @@ import org.sonar.api.config.Settings;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.Project;
+/**
+ * This interface is implemented by the views plugin!!
+ *
+ */
public interface ProfileLoader {
/**
diff --git a/sonar-batch/src/main/java/org/sonar/batch/ProfileProvider.java b/sonar-batch/src/main/java/org/sonar/batch/ProfileProvider.java
index 93012a107d2..e73f13a341b 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/ProfileProvider.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/ProfileProvider.java
@@ -20,8 +20,6 @@
package org.sonar.batch;
import org.picocontainer.injectors.ProviderAdapter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.sonar.api.config.Settings;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.Languages;
@@ -29,8 +27,6 @@ import org.sonar.api.resources.Project;
public class ProfileProvider extends ProviderAdapter {
- private static final Logger LOG = LoggerFactory.getLogger(ProfileProvider.class);
-
private RulesProfile profile;
/**
@@ -39,7 +35,6 @@ public class ProfileProvider extends ProviderAdapter {
public RulesProfile provide(Project project, ProfileLoader profileLoader, Settings settings, Languages languages) {
if (profile == null) {
profile = profileLoader.load(project, settings);
- LOG.info("Quality profile : {}", profile);
}
return profile;
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/RulesProfileWrapper.java b/sonar-batch/src/main/java/org/sonar/batch/RulesProfileWrapper.java
new file mode 100644
index 00000000000..8fcd6edf8b3
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/RulesProfileWrapper.java
@@ -0,0 +1,150 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.profiles.Alert;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.rules.ActiveRule;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.utils.SonarException;
+import org.sonar.batch.scan.language.ModuleLanguages;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This wrapper is used to try to preserver backward compatibility for plugins that used to depends on {@link RulesProfile}
+ * @since 4.2
+ */
+public class RulesProfileWrapper extends RulesProfile {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RulesProfileWrapper.class);
+
+ private final Map<String, RulesProfile> ruleProfilesPerLanguages;
+ private ModuleLanguages moduleLanguages;
+
+ public RulesProfileWrapper(ModuleLanguages moduleLanguages, Map<String, RulesProfile> ruleProfilesPerLanguages) {
+ this.moduleLanguages = moduleLanguages;
+ this.ruleProfilesPerLanguages = ruleProfilesPerLanguages;
+ }
+
+ @Override
+ public Integer getId() {
+ return getSingleProfileOrFail().getId();
+ }
+
+ private RulesProfile getSingleProfileOrFail() {
+ if (moduleLanguages.getModuleLanguageKeys().size() != 1) {
+ throw new SonarException("Please update your plugin to support multi-language analysis");
+ }
+ return ruleProfilesPerLanguages.get(moduleLanguages.getModuleLanguageKeys().iterator().next());
+ }
+
+ public RulesProfile getProfileByLanguage(String languageKey) {
+ return ruleProfilesPerLanguages.get(languageKey);
+ }
+
+ @Override
+ public String getName() {
+ return getSingleProfileOrFail().getName();
+ }
+
+ @Override
+ public String getLanguage() {
+ if (moduleLanguages.getModuleLanguageKeys().size() != 1) {
+ // FIXME This is a hack for CommonChecksDecorator that call this method in its constructor
+ LOG.debug("Please update your plugin to support multi-language analysis", new SonarException("Please update your plugin to support multi-language analysis"));
+ return "";
+ }
+ return ruleProfilesPerLanguages.get(moduleLanguages.getModuleLanguageKeys().iterator().next()).getLanguage();
+ }
+
+ @Override
+ public List<Alert> getAlerts() {
+ List<Alert> result = new ArrayList<Alert>();
+ for (RulesProfile profile : ruleProfilesPerLanguages.values()) {
+ result.addAll(profile.getAlerts());
+ }
+ return result;
+ }
+
+ @Override
+ public List<ActiveRule> getActiveRules() {
+ List<ActiveRule> activeRules = new ArrayList<ActiveRule>();
+ for (RulesProfile profile : ruleProfilesPerLanguages.values()) {
+ activeRules.addAll(profile.getActiveRules());
+ }
+ return activeRules;
+ }
+
+ @Override
+ public int getVersion() {
+ return getSingleProfileOrFail().getVersion();
+ }
+
+ @Override
+ public ActiveRule getActiveRule(String repositoryKey, String ruleKey) {
+ for (RulesProfile profile : ruleProfilesPerLanguages.values()) {
+ ActiveRule activeRule = profile.getActiveRule(repositoryKey, ruleKey);
+ if (activeRule != null) {
+ return activeRule;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public List<ActiveRule> getActiveRulesByRepository(String repositoryKey) {
+ List<ActiveRule> activeRules = new ArrayList<ActiveRule>();
+ for (RulesProfile profile : ruleProfilesPerLanguages.values()) {
+ activeRules.addAll(profile.getActiveRulesByRepository(repositoryKey));
+ }
+ return activeRules;
+ }
+
+ @Override
+ public List<ActiveRule> getActiveRules(boolean acceptDisabledRules) {
+ List<ActiveRule> activeRules = new ArrayList<ActiveRule>();
+ for (RulesProfile profile : ruleProfilesPerLanguages.values()) {
+ activeRules.addAll(profile.getActiveRules(acceptDisabledRules));
+ }
+ return activeRules;
+ }
+
+ @Override
+ public ActiveRule getActiveRule(Rule rule) {
+ for (RulesProfile profile : ruleProfilesPerLanguages.values()) {
+ ActiveRule activeRule = profile.getActiveRule(rule);
+ if (activeRule != null) {
+ return activeRule;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Boolean getDefaultProfile() {
+ return getSingleProfileOrFail().getDefaultProfile();
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java
index 6e246f19f0f..3779471971c 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java
@@ -23,6 +23,7 @@ import com.google.common.collect.Lists;
import org.apache.commons.lang.ClassUtils;
import org.sonar.api.BatchExtension;
import org.sonar.api.batch.CheckProject;
+import org.sonar.api.batch.Sensor;
import org.sonar.api.platform.ComponentContainer;
import org.sonar.api.resources.Project;
@@ -58,7 +59,9 @@ public class BatchExtensionDictionnary extends org.sonar.api.batch.BatchExtensio
private boolean shouldKeep(Class type, Object extension, Project project, ExtensionMatcher matcher) {
boolean keep = ClassUtils.isAssignable(extension.getClass(), type) && (matcher == null || matcher.accept(extension));
- if (keep && project != null && ClassUtils.isAssignable(extension.getClass(), CheckProject.class)) {
+ // For Sensors we no longer filter on shouldExecuteOnProject
+ if (keep && project != null && ClassUtils.isAssignable(extension.getClass(), CheckProject.class)
+ && !ClassUtils.isAssignable(extension.getClass(), Sensor.class)) {
keep = ((CheckProject) extension).shouldExecuteOnProject(project);
}
return keep;
diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/InitializersExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/phases/InitializersExecutor.java
index 2c26c3f002b..f0cb2033377 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/phases/InitializersExecutor.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/phases/InitializersExecutor.java
@@ -71,9 +71,7 @@ public class InitializersExecutor {
eventBus.fireEvent(new InitializerExecutionEvent(initializer, false));
}
- if (!initializers.isEmpty()) {
- fs.index();
- }
+ fs.index();
eventBus.fireEvent(new InitializersPhaseEvent(Lists.newArrayList(initializers), false));
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java
index e987b4f17df..e4f6f8d92aa 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java
@@ -29,10 +29,10 @@ import org.sonar.batch.events.EventBus;
import org.sonar.batch.index.DefaultIndex;
import org.sonar.batch.index.PersistenceManager;
import org.sonar.batch.index.ScanPersister;
-import org.sonar.batch.scan.report.JsonReport;
import org.sonar.batch.scan.filesystem.FileSystemLogger;
import org.sonar.batch.scan.maven.MavenPhaseExecutor;
import org.sonar.batch.scan.maven.MavenPluginsConfigurator;
+import org.sonar.batch.scan.report.JsonReport;
import java.util.Collection;
@@ -41,9 +41,9 @@ public final class PhaseExecutor {
public static final Logger LOGGER = LoggerFactory.getLogger(PhaseExecutor.class);
public static Collection<Class> getPhaseClasses() {
- return Lists.<Class> newArrayList(DecoratorsExecutor.class, MavenPhaseExecutor.class, MavenPluginsConfigurator.class,
- PostJobsExecutor.class, SensorsExecutor.class,
- InitializersExecutor.class, ProjectInitializer.class, UpdateStatusJob.class);
+ return Lists.<Class>newArrayList(DecoratorsExecutor.class, MavenPhaseExecutor.class, MavenPluginsConfigurator.class,
+ PostJobsExecutor.class, SensorsExecutor.class,
+ InitializersExecutor.class, ProjectInitializer.class, UpdateStatusJob.class);
}
private EventBus eventBus;
@@ -64,11 +64,11 @@ public final class PhaseExecutor {
private final JsonReport jsonReport;
public PhaseExecutor(Phases phases, DecoratorsExecutor decoratorsExecutor, MavenPhaseExecutor mavenPhaseExecutor,
- MavenPluginsConfigurator mavenPluginsConfigurator, InitializersExecutor initializersExecutor,
- PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor,
- PersistenceManager persistenceManager, SensorContext sensorContext, DefaultIndex index,
- EventBus eventBus, UpdateStatusJob updateStatusJob, ProjectInitializer pi,
- ScanPersister[] persisters, FileSystemLogger fsLogger, JsonReport jsonReport) {
+ MavenPluginsConfigurator mavenPluginsConfigurator, InitializersExecutor initializersExecutor,
+ PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor,
+ PersistenceManager persistenceManager, SensorContext sensorContext, DefaultIndex index,
+ EventBus eventBus, UpdateStatusJob updateStatusJob, ProjectInitializer pi,
+ ScanPersister[] persisters, FileSystemLogger fsLogger, JsonReport jsonReport) {
this.phases = phases;
this.decoratorsExecutor = decoratorsExecutor;
this.mavenPhaseExecutor = mavenPhaseExecutor;
@@ -88,12 +88,12 @@ public final class PhaseExecutor {
}
public PhaseExecutor(Phases phases, DecoratorsExecutor decoratorsExecutor, MavenPhaseExecutor mavenPhaseExecutor,
- MavenPluginsConfigurator mavenPluginsConfigurator, InitializersExecutor initializersExecutor,
- PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor,
- PersistenceManager persistenceManager, SensorContext sensorContext, DefaultIndex index,
- EventBus eventBus, ProjectInitializer pi, ScanPersister[] persisters, FileSystemLogger fsLogger, JsonReport jsonReport) {
+ MavenPluginsConfigurator mavenPluginsConfigurator, InitializersExecutor initializersExecutor,
+ PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor,
+ PersistenceManager persistenceManager, SensorContext sensorContext, DefaultIndex index,
+ EventBus eventBus, ProjectInitializer pi, ScanPersister[] persisters, FileSystemLogger fsLogger, JsonReport jsonReport) {
this(phases, decoratorsExecutor, mavenPhaseExecutor, mavenPluginsConfigurator, initializersExecutor, postJobsExecutor,
- sensorsExecutor, persistenceManager, sensorContext, index, eventBus, null, pi, persisters, fsLogger, jsonReport);
+ sensorsExecutor, persistenceManager, sensorContext, index, eventBus, null, pi, persisters, fsLogger, jsonReport);
}
/**
diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/ProjectInitializer.java b/sonar-batch/src/main/java/org/sonar/batch/phases/ProjectInitializer.java
index 4ade22a2e68..e4d76a7c899 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/phases/ProjectInitializer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/phases/ProjectInitializer.java
@@ -20,43 +20,40 @@
package org.sonar.batch.phases;
import org.sonar.api.BatchComponent;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
import org.sonar.api.resources.Project;
import org.sonar.api.utils.SonarException;
-import org.sonar.core.resource.ResourceDao;
-import org.sonar.core.resource.ResourceDto;
/**
* Should be dropped when org.sonar.api.resources.Project is fully refactored.
*/
public class ProjectInitializer implements BatchComponent {
- private ResourceDao resourceDao;
private Languages languages;
+ private Settings settings;
- public ProjectInitializer(ResourceDao resourceDao, Languages languages) {
- this.resourceDao = resourceDao;
+ public ProjectInitializer(Settings settings, Languages languages) {
+ this.settings = settings;
this.languages = languages;
}
public void execute(Project project) {
if (project.getLanguage() == null) {
- initLanguage(project);
+ initDeprecatedLanguage(project);
}
}
- private void initLanguage(Project project) {
- Language language = languages.get(project.getLanguageKey());
- if (language == null) {
- throw new SonarException("Language with key '" + project.getLanguageKey() + "' not found");
+ private void initDeprecatedLanguage(Project project) {
+ String languageKey = settings.getString(CoreProperties.PROJECT_LANGUAGE_PROPERTY);
+ if (languageKey != null) {
+ Language language = languages.get(languageKey);
+ if (language == null) {
+ throw new SonarException("Language with key '" + languageKey + "' not found");
+ }
+ project.setLanguage(language);
}
- project.setLanguage(language);
- if (project.getId() != null) {
- ResourceDto dto = resourceDao.getResource(project.getId());
- dto.setLanguage(project.getLanguageKey());
- resourceDao.insertOrUpdate(dto);
- }
-
}
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/SensorsExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/phases/SensorsExecutor.java
index d66e08ad0a7..117300ade10 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/phases/SensorsExecutor.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/phases/SensorsExecutor.java
@@ -23,16 +23,19 @@ import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.BatchComponent;
+import org.sonar.api.CoreProperties;
import org.sonar.api.batch.Sensor;
import org.sonar.api.batch.SensorContext;
import org.sonar.api.batch.maven.DependsUponMavenPlugin;
import org.sonar.api.batch.maven.MavenPluginHandler;
import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.resources.Language;
import org.sonar.api.resources.Project;
import org.sonar.api.utils.TimeProfiler;
import org.sonar.batch.bootstrap.BatchExtensionDictionnary;
import org.sonar.batch.events.EventBus;
import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem;
+import org.sonar.batch.scan.language.ModuleLanguages;
import org.sonar.batch.scan.maven.MavenPluginExecutor;
import java.util.Collection;
@@ -42,50 +45,70 @@ public class SensorsExecutor implements BatchComponent {
private MavenPluginExecutor mavenExecutor;
private EventBus eventBus;
- private Project project;
+ private Project module;
private DefaultModuleFileSystem fs;
private BatchExtensionDictionnary selector;
private final DatabaseSession session;
private final SensorMatcher sensorMatcher;
- private final FileIndexer fileIndexer;
+ private final ModuleLanguages moduleLanguages;
public SensorsExecutor(BatchExtensionDictionnary selector, Project project, DefaultModuleFileSystem fs, MavenPluginExecutor mavenExecutor, EventBus eventBus,
- DatabaseSession session, SensorMatcher sensorMatcher, FileIndexer fileIndexer) {
+ DatabaseSession session, SensorMatcher sensorMatcher, ModuleLanguages moduleLanguages) {
this.selector = selector;
this.mavenExecutor = mavenExecutor;
this.eventBus = eventBus;
- this.project = project;
+ this.module = project;
this.fs = fs;
this.session = session;
this.sensorMatcher = sensorMatcher;
- this.fileIndexer = fileIndexer;
+ this.moduleLanguages = moduleLanguages;
}
public void execute(SensorContext context) {
- Collection<Sensor> sensors = selector.select(Sensor.class, project, true, sensorMatcher);
+ Collection<Sensor> sensors = selector.select(Sensor.class, module, true, sensorMatcher);
eventBus.fireEvent(new SensorsPhaseEvent(Lists.newArrayList(sensors), true));
- fileIndexer.execute();
-
for (Sensor sensor : sensors) {
// SONAR-2965 In case the sensor takes too much time we close the session to not face a timeout
session.commitAndClose();
- eventBus.fireEvent(new SensorExecutionEvent(sensor, true));
- executeMavenPlugin(sensor);
- sensor.analyse(project, context);
- eventBus.fireEvent(new SensorExecutionEvent(sensor, false));
+ if (sensor.shouldExecuteOnProject(module)) {
+ executeSensor(context, sensor);
+ } else {
+ // For backward compatibility try to execute Sensor for each language until it is executed once (or never)
+ String oldLanguageKey = module.getLanguageKey();
+ Language oldLanguage = module.getLanguage();
+ for (Language language : moduleLanguages.getModuleLanguages()) {
+ module.setLanguage(language);
+ module.getConfiguration().setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, language.getKey());
+ if (sensor.shouldExecuteOnProject(module)) {
+ LOG.warn("Sensor {} should be updated to not depends on deprecated Project::getLanguage or Project::getLanguageKey", sensor);
+ executeSensor(context, sensor);
+ break;
+ }
+ }
+ // Restore module language
+ module.setLanguage(oldLanguage);
+ module.getConfiguration().setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, oldLanguageKey);
+ }
}
eventBus.fireEvent(new SensorsPhaseEvent(Lists.newArrayList(sensors), false));
}
+ private void executeSensor(SensorContext context, Sensor sensor) {
+ eventBus.fireEvent(new SensorExecutionEvent(sensor, true));
+ executeMavenPlugin(sensor);
+ sensor.analyse(module, context);
+ eventBus.fireEvent(new SensorExecutionEvent(sensor, false));
+ }
+
private void executeMavenPlugin(Sensor sensor) {
if (sensor instanceof DependsUponMavenPlugin) {
- MavenPluginHandler handler = ((DependsUponMavenPlugin) sensor).getMavenPluginHandler(project);
+ MavenPluginHandler handler = ((DependsUponMavenPlugin) sensor).getMavenPluginHandler(module);
if (handler != null) {
TimeProfiler profiler = new TimeProfiler(LOG).start("Execute maven plugin " + handler.getArtifactId());
- mavenExecutor.execute(project, fs, handler);
+ mavenExecutor.execute(module, fs, handler);
profiler.stop();
}
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
index ab083e6389e..e1c1f84e39f 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
@@ -19,6 +19,8 @@
*/
package org.sonar.batch.scan;
+import org.sonar.batch.scan.filesystem.ComponentIndexer;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.BatchExtension;
@@ -28,9 +30,11 @@ import org.sonar.api.platform.ComponentContainer;
import org.sonar.api.resources.Languages;
import org.sonar.api.resources.Project;
import org.sonar.api.scan.filesystem.FileExclusions;
+import org.sonar.batch.DefaultProfileLoader;
import org.sonar.batch.DefaultProjectClasspath;
import org.sonar.batch.DefaultSensorContext;
import org.sonar.batch.DefaultTimeMachine;
+import org.sonar.batch.ProfileLoader;
import org.sonar.batch.ProfileProvider;
import org.sonar.batch.ProjectTree;
import org.sonar.batch.ResourceFilters;
@@ -46,7 +50,6 @@ import org.sonar.batch.index.ResourcePersister;
import org.sonar.batch.issue.IssuableFactory;
import org.sonar.batch.issue.IssueFilters;
import org.sonar.batch.issue.ModuleIssues;
-import org.sonar.batch.phases.FileIndexer;
import org.sonar.batch.phases.PhaseExecutor;
import org.sonar.batch.phases.PhasesTimeProfiler;
import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem;
@@ -59,6 +62,7 @@ import org.sonar.batch.scan.filesystem.LanguageRecognizer;
import org.sonar.batch.scan.filesystem.ModuleFileSystemInitializer;
import org.sonar.batch.scan.filesystem.ProjectFileSystemAdapter;
import org.sonar.batch.scan.filesystem.RemoteFileHashes;
+import org.sonar.batch.scan.language.ModuleLanguages;
import org.sonar.batch.scan.report.ComponentSelectorFactory;
import org.sonar.batch.scan.report.JsonReport;
import org.sonar.core.component.ScanPerspectives;
@@ -91,6 +95,11 @@ public class ModuleScanContainer extends ComponentContainer {
// hack to initialize commons-configuration before ExtensionProviders
getComponentByType(ModuleSettings.class);
+ // Don't override ProfileLoader provided by views plugin in parent container
+ if (getComponentByType(ProfileLoader.class) == null) {
+ add(DefaultProfileLoader.class);
+ }
+
add(
EventBus.class,
PhaseExecutor.class,
@@ -109,7 +118,8 @@ public class ModuleScanContainer extends ComponentContainer {
FileHashes.class,
RemoteFileHashes.class,
FileIndex.class,
- FileIndexer.class,
+ ComponentIndexer.class,
+ ModuleLanguages.class,
LanguageRecognizer.class,
FileSystemLogger.class,
DefaultProjectClasspath.class,
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java
index 4438112c1d8..eec4c01995c 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java
@@ -23,17 +23,16 @@ import org.sonar.api.CoreProperties;
import org.sonar.api.platform.ComponentContainer;
import org.sonar.api.task.Task;
import org.sonar.api.task.TaskDefinition;
-import org.sonar.batch.DefaultProfileLoader;
import org.sonar.batch.DefaultProjectTree;
import org.sonar.batch.bootstrap.TaskContainer;
import org.sonar.batch.phases.Phases;
public class ScanTask implements Task {
public static final TaskDefinition DEFINITION = TaskDefinition.builder()
- .description("Scan project")
- .key(CoreProperties.SCAN_TASK)
- .taskClass(ScanTask.class)
- .build();
+ .description("Scan project")
+ .key(CoreProperties.SCAN_TASK)
+ .taskClass(ScanTask.class)
+ .build();
private final ComponentContainer taskContainer;
@@ -48,13 +47,12 @@ public class ScanTask implements Task {
// Add components specific to project scan (views will use different ones)
void scan(ComponentContainer scanContainer) {
scanContainer.add(
- new Phases().enable(Phases.Phase.values()),
- DefaultProjectTree.class,
- ProjectExclusions.class,
- ProjectReactorValidator.class,
- ProjectReactorReady.class,
- DefaultProfileLoader.class,
- DefaultSensorMatcher.class);
+ new Phases().enable(Phases.Phase.values()),
+ DefaultProjectTree.class,
+ ProjectExclusions.class,
+ ProjectReactorValidator.class,
+ ProjectReactorReady.class,
+ DefaultSensorMatcher.class);
scanContainer.execute();
}
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/FileIndexer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ComponentIndexer.java
index 880f74d44e0..ac0bfad02fe 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/phases/FileIndexer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/ComponentIndexer.java
@@ -17,10 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.batch.phases;
+package org.sonar.batch.scan.filesystem;
import com.google.common.base.CharMatcher;
import com.google.common.io.Files;
+import org.apache.commons.lang.StringUtils;
import org.sonar.api.BatchComponent;
import org.sonar.api.CoreProperties;
import org.sonar.api.batch.InstantiationStrategy;
@@ -36,32 +37,37 @@ import org.sonar.api.scan.filesystem.FileQuery;
import org.sonar.api.scan.filesystem.internal.InputFile;
import org.sonar.api.utils.SonarException;
import org.sonar.batch.index.ResourceKeyMigration;
-import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem;
+import org.sonar.batch.scan.language.ModuleLanguages;
+import org.sonar.core.resource.ResourceDao;
+import org.sonar.core.resource.ResourceDto;
/**
* Index all files/directories of the module in SQ database and importing source code.
* @since 4.2
*/
@InstantiationStrategy(InstantiationStrategy.PER_PROJECT)
-public class FileIndexer implements BatchComponent {
+public class ComponentIndexer implements BatchComponent {
- private final DefaultModuleFileSystem fs;
private final Languages languages;
private final Settings settings;
private final SonarIndex sonarIndex;
- private ResourceKeyMigration migration;
- private Project module;
+ private final ResourceKeyMigration migration;
+ private final Project module;
+ private final ModuleLanguages moduleLanguages;
+ private final ResourceDao resourceDao;
- public FileIndexer(Project module, DefaultModuleFileSystem fs, Languages languages, SonarIndex sonarIndex, Settings settings, ResourceKeyMigration migration) {
+ public ComponentIndexer(Project module, Languages languages, SonarIndex sonarIndex, Settings settings, ResourceKeyMigration migration,
+ ModuleLanguages moduleLanguages, ResourceDao resourceDao) {
this.module = module;
- this.fs = fs;
this.languages = languages;
this.sonarIndex = sonarIndex;
this.settings = settings;
this.migration = migration;
+ this.moduleLanguages = moduleLanguages;
+ this.resourceDao = resourceDao;
}
- public void execute() {
+ public void execute(DefaultModuleFileSystem fs) {
boolean importSource = settings.getBoolean(CoreProperties.CORE_IMPORT_SOURCES_PROPERTY);
Iterable<InputFile> inputFiles = fs.inputFiles(FileQuery.all());
migration.migrateIfNeeded(module, inputFiles);
@@ -75,19 +81,40 @@ public class FileIndexer implements BatchComponent {
sonarFile = File.create(inputFile.path(), inputFile.attribute(InputFile.ATTRIBUTE_SOURCE_RELATIVE_PATH), languages.get(languageKey), unitTest);
}
if (sonarFile != null) {
+ moduleLanguages.addLanguage(languageKey);
sonarIndex.index(sonarFile);
- try {
- if (importSource) {
- String source = Files.toString(inputFile.file(), inputFile.encoding());
- // SONAR-3860 Remove BOM character from source
- source = CharMatcher.anyOf("\uFEFF").removeFrom(source);
- sonarIndex.setSource(sonarFile, source);
- }
- } catch (Exception e) {
- throw new SonarException("Unable to read and import the source file : '" + inputFile.absolutePath() + "' with the charset : '"
- + inputFile.encoding() + "'.", e);
+ if (importSource) {
+ importSources(inputFile, sonarFile);
}
}
}
+
+ updateModuleLanguage();
+ }
+
+ private void importSources(InputFile inputFile, Resource sonarFile) {
+ try {
+ String source = Files.toString(inputFile.file(), inputFile.encoding());
+ // SONAR-3860 Remove BOM character from source
+ source = CharMatcher.anyOf("\uFEFF").removeFrom(source);
+ sonarIndex.setSource(sonarFile, source);
+ } catch (Exception e) {
+ throw new SonarException("Unable to read and import the source file : '" + inputFile.absolutePath() + "' with the charset : '"
+ + inputFile.encoding() + "'.", e);
+ }
+ }
+
+ private void updateModuleLanguage() {
+ if (module.getId() != null) {
+ ResourceDto dto = resourceDao.getResource(module.getId());
+ if (moduleLanguages.getModuleLanguageKeys().size() == 1) {
+ dto.setLanguage(moduleLanguages.getModuleLanguageKeys().iterator().next());
+ } else if (moduleLanguages.getModuleLanguageKeys().size() > 1) {
+ dto.setLanguage(StringUtils.join(moduleLanguages.getModuleLanguageKeys(), ","));
+ } else {
+ dto.setLanguage("none");
+ }
+ resourceDao.insertOrUpdate(dto);
+ }
}
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystem.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystem.java
index 4367b8aedd8..80bc58ad5eb 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystem.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystem.java
@@ -23,7 +23,6 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
-import org.picocontainer.Startable;
import org.sonar.api.CoreProperties;
import org.sonar.api.config.Settings;
import org.sonar.api.resources.Project;
@@ -31,6 +30,7 @@ import org.sonar.api.scan.filesystem.FileQuery;
import org.sonar.api.scan.filesystem.ModuleFileSystem;
import org.sonar.api.scan.filesystem.internal.InputFile;
import org.sonar.api.scan.filesystem.internal.InputFiles;
+import org.sonar.api.utils.SonarException;
import org.sonar.batch.bootstrap.AnalysisMode;
import javax.annotation.CheckForNull;
@@ -44,7 +44,7 @@ import java.util.List;
*
* @since 3.5
*/
-public class DefaultModuleFileSystem implements ModuleFileSystem, Startable {
+public class DefaultModuleFileSystem implements ModuleFileSystem {
private final String moduleKey;
private final FileIndex index;
@@ -57,9 +57,12 @@ public class DefaultModuleFileSystem implements ModuleFileSystem, Startable {
private List<File> sourceFiles = Lists.newArrayList();
private List<File> testFiles = Lists.newArrayList();
private AnalysisMode analysisMode;
- private boolean dirsChanged = false;
+ private ComponentIndexer componentIndexer;
+ private boolean indexed;
- public DefaultModuleFileSystem(Project module, Settings settings, FileIndex index, ModuleFileSystemInitializer initializer, AnalysisMode analysisMode) {
+ public DefaultModuleFileSystem(Project module, Settings settings, FileIndex index, ModuleFileSystemInitializer initializer, AnalysisMode analysisMode,
+ ComponentIndexer componentIndexer) {
+ this.componentIndexer = componentIndexer;
this.moduleKey = module.getKey();
this.settings = settings;
this.index = index;
@@ -123,8 +126,7 @@ public class DefaultModuleFileSystem implements ModuleFileSystem, Startable {
*/
@Deprecated
void addSourceDir(File dir) {
- sourceDirs.add(dir);
- dirsChanged = true;
+ throw new UnsupportedOperationException("Modifications of the file system are not permitted");
}
/**
@@ -133,8 +135,7 @@ public class DefaultModuleFileSystem implements ModuleFileSystem, Startable {
*/
@Deprecated
void addTestDir(File dir) {
- testDirs.add(dir);
- dirsChanged = true;
+ throw new UnsupportedOperationException("Modifications of the file system are not permitted");
}
@Override
@@ -157,10 +158,6 @@ public class DefaultModuleFileSystem implements ModuleFileSystem, Startable {
* @since 4.0
*/
public Iterable<InputFile> inputFiles(FileQuery query) {
- if (dirsChanged) {
- index();
- dirsChanged = false;
- }
List<InputFile> result = Lists.newArrayList();
FileQueryFilter filter = new FileQueryFilter(analysisMode, query);
for (InputFile input : index.inputFiles(moduleKey)) {
@@ -176,16 +173,6 @@ public class DefaultModuleFileSystem implements ModuleFileSystem, Startable {
return InputFiles.toFiles(inputFiles(query));
}
- @Override
- public void start() {
- index();
- }
-
- @Override
- public void stop() {
- // nothing to do
- }
-
public void resetDirs(File basedir, File buildDir, List<File> sourceDirs, List<File> testDirs, List<File> binaryDirs) {
Preconditions.checkNotNull(basedir, "Basedir can't be null");
this.baseDir = basedir;
@@ -193,11 +180,15 @@ public class DefaultModuleFileSystem implements ModuleFileSystem, Startable {
this.sourceDirs = existingDirs(sourceDirs);
this.testDirs = existingDirs(testDirs);
this.binaryDirs = existingDirs(binaryDirs);
- index();
}
public void index() {
+ if (indexed) {
+ throw new SonarException("Module filesystem can only be indexed once");
+ }
+ indexed = true;
index.index(this);
+ componentIndexer.execute(this);
}
private List<File> existingDirs(List<File> dirs) {
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileIndex.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileIndex.java
index d1ffbd92605..34d117be2ee 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileIndex.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileIndex.java
@@ -120,6 +120,7 @@ public class FileIndex implements BatchComponent {
}
logger.info(String.format("%d files indexed", progress.count));
+
}
private void indexFiles(DefaultModuleFileSystem fileSystem, Progress progress, List<File> sourceDirs, List<File> sourceFiles, String type) {
@@ -161,14 +162,9 @@ public class FileIndex implements BatchComponent {
@CheckForNull
private InputFile newInputFile(ModuleFileSystem fileSystem, File sourceDir, String type, File file, String path) {
- String lang = languageRecognizer.of(file);
- if (lang == null) {
- return null;
- }
Map<String, String> attributes = Maps.newHashMap();
set(attributes, InputFile.ATTRIBUTE_TYPE, type);
- set(attributes, InputFile.ATTRIBUTE_LANGUAGE, lang);
// paths
set(attributes, InputFile.ATTRIBUTE_SOURCEDIR_PATH, PathUtils.canonicalPath(sourceDir));
@@ -177,16 +173,22 @@ public class FileIndex implements BatchComponent {
String resourceKey = PathUtils.sanitize(path);
set(attributes, DefaultInputFile.ATTRIBUTE_COMPONENT_KEY, project.getEffectiveKey() + ":" + resourceKey);
+ // hash + status
+ initStatus(file, fileSystem.sourceCharset(), path, attributes);
+
+ DefaultInputFile inputFile = DefaultInputFile.create(file, fileSystem.sourceCharset(), path, attributes);
+ String lang = languageRecognizer.of(inputFile);
+ if (lang == null) {
+ return null;
+ }
+ set(inputFile.attributes(), InputFile.ATTRIBUTE_LANGUAGE, lang);
if (Java.KEY.equals(lang)) {
- set(attributes, DefaultInputFile.ATTRIBUTE_COMPONENT_DEPRECATED_KEY, project.getEffectiveKey() + ":"
+ set(inputFile.attributes(), DefaultInputFile.ATTRIBUTE_COMPONENT_DEPRECATED_KEY, project.getEffectiveKey() + ":"
+ JavaFile.fromRelativePath(sourceRelativePath, false).getDeprecatedKey());
} else {
- set(attributes, DefaultInputFile.ATTRIBUTE_COMPONENT_DEPRECATED_KEY, project.getEffectiveKey() + ":" + sourceRelativePath);
+ set(inputFile.attributes(), DefaultInputFile.ATTRIBUTE_COMPONENT_DEPRECATED_KEY, project.getEffectiveKey() + ":" + sourceRelativePath);
}
- // hash + status
- initStatus(file, fileSystem.sourceCharset(), path, attributes);
-
- return DefaultInputFile.create(file, fileSystem.sourceCharset(), path, attributes);
+ return inputFile;
}
private void initStatus(File file, Charset charset, String baseRelativePath, Map<String, String> attributes) {
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/LanguageRecognizer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/LanguageRecognizer.java
index fa8269afaad..26d898ff2ea 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/LanguageRecognizer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/LanguageRecognizer.java
@@ -20,79 +20,126 @@
package org.sonar.batch.scan.filesystem;
import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Maps;
import com.google.common.collect.SetMultimap;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.picocontainer.Startable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.sonar.api.BatchComponent;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
import org.sonar.api.resources.Language;
-import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.scan.filesystem.internal.InputFile;
+import org.sonar.api.utils.SonarException;
import javax.annotation.CheckForNull;
-import java.io.File;
+
+import java.util.Map;
import java.util.Set;
/**
- * Detect language of source files. Simplistic, based on file extensions.
+ * Detect language of source files.
*/
public class LanguageRecognizer implements BatchComponent, Startable {
- private final Project project;
- private final Language[] languages;
+ private static final Logger LOG = LoggerFactory.getLogger(LanguageRecognizer.class);
+
+ private final Languages languages;
/**
* Lower-case extension -> languages
*/
private SetMultimap<String, String> langsByExtension = HashMultimap.create();
+ private Map<String, PathPattern[]> patternByLanguage = Maps.newLinkedHashMap();
- /**
- * Some plugins, like web and cobol, can analyze all the source files, whatever
- * their file extension. This behavior is kept for backward-compatibility,
- * but it should be fixed with future multi-language support.
- */
- private boolean ignoreFileExtension = false;
+ private Settings settings;
- public LanguageRecognizer(Project project, Language[] languages) {
- this.project = project;
+ public LanguageRecognizer(Settings settings, Languages languages) {
+ this.settings = settings;
this.languages = languages;
}
- /**
- * When no language plugin is installed
- */
- public LanguageRecognizer(Project project) {
- this(project, new Language[0]);
- }
-
@Override
public void start() {
- for (Language language : languages) {
- if (language.getFileSuffixes().length == 0 && language.getKey().equals(project.getLanguageKey())) {
- ignoreFileExtension = true;
-
- } else {
- for (String suffix : language.getFileSuffixes()) {
- String extension = sanitizeExtension(suffix);
- langsByExtension.put(extension, language.getKey());
- }
+ for (Language language : languages.all()) {
+ for (String suffix : language.getFileSuffixes()) {
+ String extension = sanitizeExtension(suffix);
+ langsByExtension.put(extension, language.getKey());
+ }
+ String[] filePatterns = settings.getStringArray(getFilePatternPropKey(language.getKey()));
+ PathPattern[] pathPatterns = PathPattern.create(filePatterns);
+ if (pathPatterns.length > 0) {
+ patternByLanguage.put(language.getKey(), pathPatterns);
}
}
}
+ private String getFilePatternPropKey(String languageKey) {
+ return "sonar." + languageKey + ".filePatterns";
+ }
+
@Override
public void stop() {
// do nothing
}
@CheckForNull
- String of(File file) {
- if (ignoreFileExtension) {
- return project.getLanguageKey();
+ String of(InputFile inputFile) {
+ // First try with patterns
+ String forcedLanguage = null;
+ for (Map.Entry<String, PathPattern[]> languagePattern : patternByLanguage.entrySet()) {
+ PathPattern[] patterns = languagePattern.getValue();
+ for (PathPattern pathPattern : patterns) {
+ if (pathPattern.match(inputFile)) {
+ if (forcedLanguage == null) {
+ forcedLanguage = languagePattern.getKey();
+ break;
+ } else {
+ // Language was already forced by another pattern
+ throw new SonarException("Language of file '" + inputFile.path() + "' can not be decided as the file matches patterns of both " + getFilePatternPropKey(forcedLanguage)
+ + " and "
+ + getFilePatternPropKey(languagePattern.getKey()));
+ }
+ }
+ }
+ }
+ if (forcedLanguage != null) {
+ LOG.debug("Language of file '" + inputFile.path() + "' was forced to '" + forcedLanguage + "'");
+ return forcedLanguage;
+ }
+
+ String extension = sanitizeExtension(FilenameUtils.getExtension(inputFile.file().getName()));
+
+ // Check if deprecated sonar.language is used
+ String languageKey = settings.getString(CoreProperties.PROJECT_LANGUAGE_PROPERTY);
+ if (StringUtils.isNotBlank(languageKey)) {
+ Language language = languages.get(languageKey);
+ if (language == null) {
+ throw new SonarException("No language is installed with key '" + languageKey + "'. Please update property '" + CoreProperties.PROJECT_LANGUAGE_PROPERTY + "'");
+ }
+ // Languages without declared suffixes match everything
+ String[] fileSuffixes = language.getFileSuffixes();
+ if (fileSuffixes.length == 0) {
+ return languageKey;
+ }
+ for (String fileSuffix : fileSuffixes) {
+ if (sanitizeExtension(fileSuffix).equals(extension)) {
+ return languageKey;
+ }
+ }
+ return null;
}
- // multi-language is not supported yet. Filter on project language
- String extension = sanitizeExtension(FilenameUtils.getExtension(file.getName()));
+
+ // At this point use extension to detect language
Set<String> langs = langsByExtension.get(extension);
- return langs.contains(project.getLanguageKey()) ? project.getLanguageKey() : null;
+ if (langs.size() > 1) {
+ throw new SonarException("Language of file '" + inputFile.path() + "' can not be decided as the file extension '" + extension + "' is declared by several languages: "
+ + StringUtils.join(langs, ", "));
+ }
+ return langs.isEmpty() ? null : langs.iterator().next();
}
static String sanitizeExtension(String suffix) {
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/PathPattern.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/PathPattern.java
index 446052c84c5..650f447e476 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/PathPattern.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/PathPattern.java
@@ -81,7 +81,7 @@ abstract class PathPattern {
}
/**
- * Path relative to source directory
+ * Path relative to module basedir
*/
private static class RelativePathPattern extends PathPattern {
private RelativePathPattern(String pattern) {
@@ -90,7 +90,7 @@ abstract class PathPattern {
@Override
boolean match(InputFile inputFile) {
- String path = inputFile.attribute(InputFile.ATTRIBUTE_SOURCE_RELATIVE_PATH);
+ String path = inputFile.path();
return path != null && pattern.match(path);
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/language/ModuleLanguages.java b/sonar-batch/src/main/java/org/sonar/batch/scan/language/ModuleLanguages.java
new file mode 100644
index 00000000000..fa986815858
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/language/ModuleLanguages.java
@@ -0,0 +1,71 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.scan.language;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.BatchExtension;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.utils.SonarException;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Give access to all languages detected on the current module
+ * @since 4.2
+ */
+public class ModuleLanguages implements BatchExtension {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ModuleLanguages.class);
+
+ private Map<String, Language> moduleLanguages = new HashMap<String, Language>();
+
+ private Languages languages;
+
+ public ModuleLanguages(Settings settings, Languages languages) {
+ this.languages = languages;
+ if (settings.hasKey(CoreProperties.PROJECT_LANGUAGE_PROPERTY)) {
+ String languageKey = settings.getString(CoreProperties.PROJECT_LANGUAGE_PROPERTY);
+ LOG.info("Language is forced to {}", languageKey);
+ addLanguage(languageKey);
+ }
+ }
+
+ public void addLanguage(String languageKey) {
+ Language language = languages.get(languageKey);
+ if (language == null) {
+ throw new SonarException("Unknow language " + languageKey);
+ }
+ moduleLanguages.put(languageKey, language);
+ }
+
+ public Collection<String> getModuleLanguageKeys() {
+ return moduleLanguages.keySet();
+ }
+
+ public Collection<Language> getModuleLanguages() {
+ return moduleLanguages.values();
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/DefaultProfileLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/DefaultProfileLoaderTest.java
index 693d4d67f09..f29ba9b9a1e 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/DefaultProfileLoaderTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/DefaultProfileLoaderTest.java
@@ -25,15 +25,21 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.config.Settings;
+import org.sonar.api.profiles.Alert;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.AbstractLanguage;
import org.sonar.api.resources.Java;
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
import org.sonar.api.resources.Project;
+import org.sonar.api.rules.ActiveRule;
+import org.sonar.api.rules.RulePriority;
import org.sonar.api.utils.SonarException;
+import org.sonar.batch.scan.language.ModuleLanguages;
import org.sonar.jpa.dao.ProfilesDao;
+import java.util.Arrays;
+
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -50,50 +56,105 @@ public class DefaultProfileLoaderTest {
@Before
public void setUp() {
dao = mock(ProfilesDao.class);
- Language java = new AbstractLanguage("java", "Java") {
- public String[] getFileSuffixes() {
- return null;
- };
- };
- Language cobol = new AbstractLanguage("js", "JavaScript") {
+ Language cobol = new AbstractLanguage("cobol", "Cobol") {
public String[] getFileSuffixes() {
return null;
};
};
- languages = new Languages(java, cobol);
+ languages = new Languages(Java.INSTANCE, cobol);
}
@Test
public void should_get_configured_profile() {
Settings settings = new Settings();
settings.setProperty("sonar.profile.java", "legacy profile");
+ settings.setProperty("sonar.profile.cobol", "cobol profile");
when(dao.getProfile(Java.KEY, "legacy profile")).thenReturn(RulesProfile.create("legacy profile", "java"));
+ when(dao.getProfile("cobol", "cobol profile")).thenReturn(RulesProfile.create("cobol profile", "cobol"));
- RulesProfile profile = new DefaultProfileLoader(dao).load(javaProject, settings);
+ ModuleLanguages moduleLanguages = new ModuleLanguages(settings, languages);
+ moduleLanguages.addLanguage("java");
+ RulesProfile profile = new DefaultProfileLoader(dao, moduleLanguages, languages).load(javaProject, settings);
assertThat(profile.getName()).isEqualTo("legacy profile");
}
@Test
- public void should_fail_if_not_found() {
+ public void some_methods_should_support_multilanguage() {
Settings settings = new Settings();
- settings.setProperty("sonar.profile.java", "unknown");
+ settings.setProperty("sonar.profile.java", "java profile");
+ settings.setProperty("sonar.profile.cobol", "cobol profile");
+
+ RulesProfile javaProfile = RulesProfile.create("java profile", "java");
+ org.sonar.api.rules.Rule javaRule = new org.sonar.api.rules.Rule("javaplugin", "javarule");
+ ActiveRule javaActiveRule = new ActiveRule(javaProfile, javaRule, RulePriority.BLOCKER);
+ javaProfile.addActiveRule(javaActiveRule);
+ Alert javaAlert = mock(Alert.class);
+ javaProfile.setAlerts(Arrays.asList(javaAlert));
+ when(dao.getProfile(Java.KEY, "java profile")).thenReturn(javaProfile);
+
+ RulesProfile cobolProfile = RulesProfile.create("cobol profile", "cobol");
+ org.sonar.api.rules.Rule cobolRule = new org.sonar.api.rules.Rule("cobolplugin", "cobolrule");
+ ActiveRule cobolActiveRule = new ActiveRule(cobolProfile, cobolRule, RulePriority.BLOCKER);
+ cobolProfile.addActiveRule(cobolActiveRule);
+ Alert cobolAlert = mock(Alert.class);
+ cobolProfile.setAlerts(Arrays.asList(cobolAlert));
+ when(dao.getProfile("cobol", "cobol profile")).thenReturn(cobolProfile);
+
+ ModuleLanguages moduleLanguages = new ModuleLanguages(settings, languages);
+ moduleLanguages.addLanguage("java");
+ moduleLanguages.addLanguage("cobol");
+ RulesProfile profile = new DefaultProfileLoader(dao, moduleLanguages, languages).load(javaProject, settings);
+
+ assertThat(profile.getActiveRules()).containsOnly(javaActiveRule, cobolActiveRule);
+ assertThat(profile.getActiveRules(true)).containsOnly(javaActiveRule, cobolActiveRule);
+ assertThat(profile.getActiveRulesByRepository("javaplugin")).containsOnly(javaActiveRule);
+ assertThat(profile.getActiveRulesByRepository("cobolplugin")).containsOnly(cobolActiveRule);
+ assertThat(profile.getActiveRule("javaplugin", "javarule")).isEqualTo(javaActiveRule);
+ assertThat(profile.getActiveRule(javaRule)).isEqualTo(javaActiveRule);
+ assertThat(profile.getActiveRule("cobolplugin", "cobolrule")).isEqualTo(cobolActiveRule);
+ assertThat(profile.getActiveRule(cobolRule)).isEqualTo(cobolActiveRule);
+ assertThat(profile.getAlerts()).containsOnly(javaAlert, cobolAlert);
+ // Hack for CommonChecksDecorator
+ assertThat(profile.getLanguage()).isEqualTo("");
+ }
+
+ @Test
+ public void some_methods_should_not_support_multilanguage() {
+ Settings settings = new Settings();
+ settings.setProperty("sonar.profile.java", "java profile");
+ settings.setProperty("sonar.profile.cobol", "cobol profile");
+
+ RulesProfile javaProfile = RulesProfile.create("java profile", "java");
+ org.sonar.api.rules.Rule javaRule = new org.sonar.api.rules.Rule("javaplugin", "javarule");
+ ActiveRule javaActiveRule = new ActiveRule(javaProfile, javaRule, RulePriority.BLOCKER);
+ javaProfile.addActiveRule(javaActiveRule);
+ when(dao.getProfile(Java.KEY, "java profile")).thenReturn(javaProfile);
+
+ RulesProfile cobolProfile = RulesProfile.create("cobol profile", "cobol");
+ org.sonar.api.rules.Rule cobolRule = new org.sonar.api.rules.Rule("cobolplugin", "cobolrule");
+ ActiveRule cobolActiveRule = new ActiveRule(cobolProfile, cobolRule, RulePriority.BLOCKER);
+ cobolProfile.addActiveRule(cobolActiveRule);
+ when(dao.getProfile("cobol", "cobol profile")).thenReturn(cobolProfile);
+
+ ModuleLanguages moduleLanguages = new ModuleLanguages(settings, languages);
+ moduleLanguages.addLanguage("java");
+ moduleLanguages.addLanguage("cobol");
+ RulesProfile profile = new DefaultProfileLoader(dao, moduleLanguages, languages).load(javaProject, settings);
thrown.expect(SonarException.class);
- thrown.expectMessage("Quality profile not found : unknown, language java");
- new DefaultProfileLoader(dao).load(javaProject, settings);
+ thrown.expectMessage("Please update your plugin to support multi-language analysis");
+ profile.getId();
}
- /**
- * SONAR-3125
- */
@Test
- public void should_give_explicit_message_if_default_profile_not_found() {
- Project cobolProject = newProject("cobol");
+ public void should_fail_if_not_found() {
+ Settings settings = new Settings();
+ settings.setProperty("sonar.profile.java", "unknown");
thrown.expect(SonarException.class);
- thrown.expectMessage("You must install a plugin that supports the language key 'cobol'");
- new DefaultProfileLoader(dao).load(cobolProject, new Settings());
+ thrown.expectMessage("Quality profile not found : unknown, language java");
+ new DefaultProfileLoader(dao, new ModuleLanguages(settings, languages), languages).load(javaProject, settings);
}
private Project newProject(String language) {
diff --git a/sonar-batch/src/test/java/org/sonar/batch/phases/FileIndexerTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/ComponentIndexerTest.java
index e029d097752..3b00cc1c9b1 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/phases/FileIndexerTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/ComponentIndexerTest.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.batch.phases;
+package org.sonar.batch.scan.filesystem;
import com.google.common.base.Charsets;
import org.apache.commons.io.FileUtils;
@@ -43,7 +43,8 @@ import org.sonar.api.scan.filesystem.internal.DefaultInputFile;
import org.sonar.api.scan.filesystem.internal.InputFile;
import org.sonar.api.scan.filesystem.internal.InputFileBuilder;
import org.sonar.batch.index.ResourceKeyMigration;
-import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem;
+import org.sonar.batch.scan.language.ModuleLanguages;
+import org.sonar.core.resource.ResourceDao;
import java.io.File;
import java.io.IOException;
@@ -56,7 +57,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-public class FileIndexerTest {
+public class ComponentIndexerTest {
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@@ -95,8 +96,10 @@ public class FileIndexerTest {
newInputFile("src/main/java2/foo/bar/Foo.java", "", "foo/bar/Foo.java", "java", false),
newInputFile("src/test/java/foo/bar/FooTest.java", "", "foo/bar/FooTest.java", "java", true)));
when(project.getLanguageKey()).thenReturn(Java.KEY);
- FileIndexer indexer = new FileIndexer(project, fs, new Languages(Java.INSTANCE), sonarIndex, settings, mock(ResourceKeyMigration.class));
- indexer.execute();
+ Languages languages = new Languages(Java.INSTANCE);
+ ComponentIndexer indexer = new ComponentIndexer(project, languages, sonarIndex, settings, mock(ResourceKeyMigration.class), new ModuleLanguages(settings, languages),
+ mock(ResourceDao.class));
+ indexer.execute(fs);
verify(sonarIndex).index(JavaFile.create("src/main/java/foo/bar/Foo.java", "foo/bar/Foo.java", false));
verify(sonarIndex).index(JavaFile.create("src/main/java2/foo/bar/Foo.java", "foo/bar/Foo.java", false));
@@ -119,8 +122,10 @@ public class FileIndexerTest {
newInputFile("src/test/foo/bar/FooTest.cbl", "", "foo/bar/FooTest.cbl", "cobol", true)));
when(project.getLanguageKey()).thenReturn("cobol");
- FileIndexer indexer = new FileIndexer(project, fs, new Languages(cobolLanguage), sonarIndex, settings, mock(ResourceKeyMigration.class));
- indexer.execute();
+ Languages languages = new Languages(cobolLanguage);
+ ComponentIndexer indexer = new ComponentIndexer(project, languages, sonarIndex, settings, mock(ResourceKeyMigration.class), new ModuleLanguages(settings, languages),
+ mock(ResourceDao.class));
+ indexer.execute(fs);
verify(sonarIndex).index(org.sonar.api.resources.File.create("/src/foo/bar/Foo.cbl", "foo/bar/Foo.cbl", cobolLanguage, false));
verify(sonarIndex).index(org.sonar.api.resources.File.create("/src2/foo/bar/Foo.cbl", "foo/bar/Foo.cbl", cobolLanguage, false));
@@ -134,8 +139,10 @@ public class FileIndexerTest {
when(fs.inputFiles(FileQuery.all())).thenReturn((Iterable) Arrays.asList(
newInputFile("src/main/java/foo/bar/Foo.java", "sample code", "foo/bar/Foo.java", "java", false)));
when(project.getLanguageKey()).thenReturn(Java.KEY);
- FileIndexer indexer = new FileIndexer(project, fs, new Languages(Java.INSTANCE), sonarIndex, settings, mock(ResourceKeyMigration.class));
- indexer.execute();
+ Languages languages = new Languages(Java.INSTANCE);
+ ComponentIndexer indexer = new ComponentIndexer(project, languages, sonarIndex, settings, mock(ResourceKeyMigration.class), new ModuleLanguages(settings, languages),
+ mock(ResourceDao.class));
+ indexer.execute(fs);
Resource sonarFile = JavaFile.create("src/main/java/foo/bar/Foo.java", "foo/bar/Foo.java", false);
verify(sonarIndex).index(sonarFile);
@@ -185,8 +192,10 @@ public class FileIndexerTest {
.attribute(InputFile.ATTRIBUTE_LANGUAGE, "java")
.build()));
when(project.getLanguageKey()).thenReturn(Java.KEY);
- FileIndexer indexer = new FileIndexer(project, fs, new Languages(Java.INSTANCE), sonarIndex, settings, mock(ResourceKeyMigration.class));
- indexer.execute();
+ Languages languages = new Languages(Java.INSTANCE);
+ ComponentIndexer indexer = new ComponentIndexer(project, languages, sonarIndex, settings, mock(ResourceKeyMigration.class), new ModuleLanguages(settings, languages),
+ mock(ResourceDao.class));
+ indexer.execute(fs);
Resource sonarFile = JavaFile.create("src/main/java/foo/bar/Foo.java", "foo/bar/Foo.java", false);
@@ -212,8 +221,10 @@ public class FileIndexerTest {
.attribute(InputFile.ATTRIBUTE_LANGUAGE, "java")
.build()));
when(project.getLanguageKey()).thenReturn(Java.KEY);
- FileIndexer indexer = new FileIndexer(project, fs, new Languages(Java.INSTANCE), sonarIndex, settings, mock(ResourceKeyMigration.class));
- indexer.execute();
+ Languages languages = new Languages(Java.INSTANCE);
+ ComponentIndexer indexer = new ComponentIndexer(project, languages, sonarIndex, settings, mock(ResourceKeyMigration.class), new ModuleLanguages(settings, languages),
+ mock(ResourceDao.class));
+ indexer.execute(fs);
Resource sonarFile = JavaFile.create("/src/main/java/foo/bar/Foo.java", "foo/bar/Foo.java", false);
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystemTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystemTest.java
index 6c0dbbe079a..ef1298b4514 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystemTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/DefaultModuleFileSystemTest.java
@@ -60,18 +60,20 @@ public class DefaultModuleFileSystemTest {
FileIndex fileIndex = mock(FileIndex.class);
ModuleFileSystemInitializer initializer = mock(ModuleFileSystemInitializer.class, Mockito.RETURNS_DEEP_STUBS);
private AnalysisMode mode;
+ private ComponentIndexer componentIndexer;
@Before
public void before() {
mode = mock(AnalysisMode.class);
+ componentIndexer = mock(ComponentIndexer.class);
}
@Test
public void test_equals_and_hashCode() throws Exception {
- DefaultModuleFileSystem foo1 = new DefaultModuleFileSystem(new Project("foo"), settings, fileIndex, initializer, mode);
- DefaultModuleFileSystem foo2 = new DefaultModuleFileSystem(new Project("foo"), settings, fileIndex, initializer, mode);
- DefaultModuleFileSystem bar = new DefaultModuleFileSystem(new Project("bar"), settings, fileIndex, initializer, mode);
- DefaultModuleFileSystem branch = new DefaultModuleFileSystem(new Project("bar", "branch", "My project"), settings, fileIndex, initializer, mode);
+ DefaultModuleFileSystem foo1 = new DefaultModuleFileSystem(new Project("foo"), settings, fileIndex, initializer, mode, componentIndexer);
+ DefaultModuleFileSystem foo2 = new DefaultModuleFileSystem(new Project("foo"), settings, fileIndex, initializer, mode, componentIndexer);
+ DefaultModuleFileSystem bar = new DefaultModuleFileSystem(new Project("bar"), settings, fileIndex, initializer, mode, componentIndexer);
+ DefaultModuleFileSystem branch = new DefaultModuleFileSystem(new Project("bar", "branch", "My project"), settings, fileIndex, initializer, mode, componentIndexer);
assertThat(foo1.moduleKey()).isEqualTo("foo");
assertThat(branch.moduleKey()).isEqualTo("bar:branch");
@@ -85,7 +87,7 @@ public class DefaultModuleFileSystemTest {
@Test
public void default_source_encoding() {
- DefaultModuleFileSystem fs = new DefaultModuleFileSystem(new Project("foo"), settings, fileIndex, initializer, mode);
+ DefaultModuleFileSystem fs = new DefaultModuleFileSystem(new Project("foo"), settings, fileIndex, initializer, mode, componentIndexer);
assertThat(fs.sourceCharset()).isEqualTo(Charset.defaultCharset());
assertThat(fs.isDefaultSourceCharset()).isTrue();
@@ -94,7 +96,7 @@ public class DefaultModuleFileSystemTest {
@Test
public void source_encoding_is_set() {
settings.setProperty(CoreProperties.ENCODING_PROPERTY, "Cp1124");
- DefaultModuleFileSystem fs = new DefaultModuleFileSystem(new Project("foo"), settings, fileIndex, initializer, mode);
+ DefaultModuleFileSystem fs = new DefaultModuleFileSystem(new Project("foo"), settings, fileIndex, initializer, mode, componentIndexer);
assertThat(fs.sourceCharset()).isEqualTo(Charset.forName("Cp1124"));
@@ -118,7 +120,7 @@ public class DefaultModuleFileSystemTest {
when(initializer.additionalSourceFiles()).thenReturn(Arrays.asList(additionalFile));
when(initializer.additionalTestFiles()).thenReturn(Arrays.asList(additionalTest));
- DefaultModuleFileSystem fs = new DefaultModuleFileSystem(new Project("foo"), settings, fileIndex, initializer, mode);
+ DefaultModuleFileSystem fs = new DefaultModuleFileSystem(new Project("foo"), settings, fileIndex, initializer, mode, componentIndexer);
assertThat(fs.baseDir().getCanonicalPath()).isEqualTo(basedir.getCanonicalPath());
assertThat(fs.workingDir().getCanonicalPath()).isEqualTo(workingDir.getCanonicalPath());
@@ -137,7 +139,7 @@ public class DefaultModuleFileSystemTest {
when(initializer.workingDir()).thenReturn(basedir);
when(initializer.sourceDirs()).thenReturn(Arrays.asList(new File(basedir, "src/main/java")));
- DefaultModuleFileSystem fs = new DefaultModuleFileSystem(new Project("foo"), settings, fileIndex, initializer, mode);
+ DefaultModuleFileSystem fs = new DefaultModuleFileSystem(new Project("foo"), settings, fileIndex, initializer, mode, componentIndexer);
File existingDir = temp.newFolder("new_folder");
File notExistingDir = new File(existingDir, "not_exist");
@@ -153,12 +155,11 @@ public class DefaultModuleFileSystemTest {
assertThat(fs.testDirs().get(0).getCanonicalPath()).isEqualTo(existingDir.getCanonicalPath());
assertThat(fs.binaryDirs()).hasSize(1);
assertThat(fs.binaryDirs().get(0).getCanonicalPath()).isEqualTo(existingDir.getCanonicalPath());
- verify(fileIndex).index(fs);
}
@Test
public void should_search_input_files() throws Exception {
- DefaultModuleFileSystem fs = new DefaultModuleFileSystem(new Project("foo"), settings, fileIndex, initializer, mode);
+ DefaultModuleFileSystem fs = new DefaultModuleFileSystem(new Project("foo"), settings, fileIndex, initializer, mode, componentIndexer);
File mainFile = temp.newFile();
InputFile mainInput = DefaultInputFile.create(mainFile, Charsets.UTF_8, "Main.java", ImmutableMap.of(InputFile.ATTRIBUTE_TYPE, InputFile.TYPE_SOURCE));
@@ -175,13 +176,13 @@ public class DefaultModuleFileSystemTest {
@Test
public void should_index() throws Exception {
- DefaultModuleFileSystem fs = new DefaultModuleFileSystem(new Project("foo"), settings, fileIndex, initializer, mode);
+ DefaultModuleFileSystem fs = new DefaultModuleFileSystem(new Project("foo"), settings, fileIndex, initializer, mode, componentIndexer);
verifyZeroInteractions(fileIndex);
- fs.start();
+ fs.index();
verify(fileIndex).index(fs);
- fs.stop();
+ verify(componentIndexer).execute(fs);
}
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/LanguageRecognizerTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/LanguageRecognizerTest.java
index 3903cd11df0..dad6ae8e240 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/LanguageRecognizerTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/LanguageRecognizerTest.java
@@ -19,28 +19,35 @@
*/
package org.sonar.batch.scan.filesystem;
+import com.google.common.base.Charsets;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
-import org.mockito.ArgumentMatcher;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
import org.sonar.api.resources.Language;
-import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.scan.filesystem.internal.InputFile;
+import org.sonar.api.scan.filesystem.internal.InputFileBuilder;
+import org.sonar.api.utils.SonarException;
-import java.util.Set;
+import java.io.File;
+import java.io.IOException;
import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
public class LanguageRecognizerTest {
@Rule
public TemporaryFolder temp = new TemporaryFolder();
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
@Test
public void test_sanitizeExtension() throws Exception {
assertThat(LanguageRecognizer.sanitizeExtension(".cbl")).isEqualTo("cbl");
@@ -51,65 +58,150 @@ public class LanguageRecognizerTest {
@Test
public void search_by_file_extension() throws Exception {
- Language[] languages = new Language[] {new MockLanguage("java", "java", "jav"), new MockLanguage("cobol", "cbl", "cob")};
- LanguageRecognizer recognizer = new LanguageRecognizer(newProject("java"), languages);
+ Languages languages = new Languages(new MockLanguage("java", "java", "jav"), new MockLanguage("cobol", "cbl", "cob"));
+ LanguageRecognizer recognizer = new LanguageRecognizer(new Settings(), languages);
recognizer.start();
- assertThat(recognizer.of(temp.newFile("Foo.java"))).isEqualTo("java");
- assertThat(recognizer.of(temp.newFile("Foo.JAVA"))).isEqualTo("java");
- assertThat(recognizer.of(temp.newFile("Foo.jav"))).isEqualTo("java");
- assertThat(recognizer.of(temp.newFile("Foo.Jav"))).isEqualTo("java");
-
- // multi-language is not supported yet -> filter on project language
- assertThat(recognizer.of(temp.newFile("abc.cbl"))).isNull();
- assertThat(recognizer.of(temp.newFile("abc.CBL"))).isNull();
- assertThat(recognizer.of(temp.newFile("abc.php"))).isNull();
- assertThat(recognizer.of(temp.newFile("abc"))).isNull();
+ assertThat(recognizer.of(newInputFile("Foo.java"))).isEqualTo("java");
+ assertThat(recognizer.of(newInputFile("Foo.JAVA"))).isEqualTo("java");
+ assertThat(recognizer.of(newInputFile("Foo.jav"))).isEqualTo("java");
+ assertThat(recognizer.of(newInputFile("Foo.Jav"))).isEqualTo("java");
+
+ assertThat(recognizer.of(newInputFile("abc.cbl"))).isEqualTo("cobol");
+ assertThat(recognizer.of(newInputFile("abc.CBL"))).isEqualTo("cobol");
+
+ assertThat(recognizer.of(newInputFile("abc.php"))).isNull();
+ assertThat(recognizer.of(newInputFile("abc"))).isNull();
recognizer.stop();
}
@Test
public void should_not_fail_if_no_language() throws Exception {
- LanguageRecognizer recognizer = spy(new LanguageRecognizer(newProject("java")));
+ LanguageRecognizer recognizer = spy(new LanguageRecognizer(new Settings(), new Languages()));
recognizer.start();
- assertThat(recognizer.of(temp.newFile("Foo.java"))).isNull();
+ assertThat(recognizer.of(newInputFile("Foo.java"))).isNull();
}
@Test
public void plugin_can_declare_a_file_extension_twice_for_case_sensitivity() throws Exception {
- Language[] languages = new Language[] {new MockLanguage("abap", "abap", "ABAP")};
+ Languages languages = new Languages(new MockLanguage("abap", "abap", "ABAP"));
- LanguageRecognizer recognizer = new LanguageRecognizer(newProject("abap"), languages);
+ LanguageRecognizer recognizer = new LanguageRecognizer(new Settings(), languages);
recognizer.start();
- assertThat(recognizer.of(temp.newFile("abc.abap"))).isEqualTo("abap");
+ assertThat(recognizer.of(newInputFile("abc.abap"))).isEqualTo("abap");
}
@Test
public void language_with_no_extension() throws Exception {
// abap does not declare any file extensions.
// When analyzing an ABAP project, then all source files must be parsed.
- Language[] languages = new Language[] {new MockLanguage("java", "java"), new MockLanguage("abap")};
+ Languages languages = new Languages(new MockLanguage("java", "java"), new MockLanguage("abap"));
// No side-effect on non-ABAP projects
- LanguageRecognizer recognizer = new LanguageRecognizer(newProject("java"), languages);
+ LanguageRecognizer recognizer = new LanguageRecognizer(new Settings(), languages);
+ recognizer.start();
+ assertThat(recognizer.of(newInputFile("abc"))).isNull();
+ assertThat(recognizer.of(newInputFile("abc.abap"))).isNull();
+ assertThat(recognizer.of(newInputFile("abc.java"))).isEqualTo("java");
+ recognizer.stop();
+
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, "abap");
+ recognizer = new LanguageRecognizer(settings, languages);
+ recognizer.start();
+ assertThat(recognizer.of(newInputFile("abc"))).isEqualTo("abap");
+ assertThat(recognizer.of(newInputFile("abc.txt"))).isEqualTo("abap");
+ assertThat(recognizer.of(newInputFile("abc.java"))).isEqualTo("abap");
+ recognizer.stop();
+ }
+
+ @Test
+ public void force_language_using_deprecated_property() throws Exception {
+ Languages languages = new Languages(new MockLanguage("java", "java"), new MockLanguage("php", "php"));
+
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, "java");
+ LanguageRecognizer recognizer = new LanguageRecognizer(settings, languages);
+ recognizer.start();
+ assertThat(recognizer.of(newInputFile("abc"))).isNull();
+ assertThat(recognizer.of(newInputFile("abc.php"))).isNull();
+ assertThat(recognizer.of(newInputFile("abc.java"))).isEqualTo("java");
+ recognizer.stop();
+ }
+
+ @Test
+ public void fail_if_invalid_language() throws Exception {
+ Languages languages = new Languages(new MockLanguage("java", "java"), new MockLanguage("php", "php"));
+
+ Settings settings = new Settings();
+ settings.setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, "unknow");
+ LanguageRecognizer recognizer = new LanguageRecognizer(settings, languages);
recognizer.start();
- assertThat(recognizer.of(temp.newFile("abc"))).isNull();
- assertThat(recognizer.of(temp.newFile("abc.abap"))).isNull();
- assertThat(recognizer.of(temp.newFile("abc.java"))).isEqualTo("java");
+ thrown.expect(SonarException.class);
+ thrown.expectMessage("No language is installed with key 'unknow'. Please update property 'sonar.language'");
+ recognizer.of(newInputFile("abc"));
recognizer.stop();
+ }
+
+ @Test
+ public void fail_if_conflicting_language_suffix() throws Exception {
+ Languages languages = new Languages(new MockLanguage("xml", "xhtml"), new MockLanguage("web", "xhtml"));
+
+ Settings settings = new Settings();
+ LanguageRecognizer recognizer = new LanguageRecognizer(settings, languages);
+ recognizer.start();
+ thrown.expect(SonarException.class);
+ thrown.expectMessage(new BaseMatcher<String>() {
+ @Override
+ public void describeTo(Description arg0) {
+ }
+
+ @Override
+ public boolean matches(Object arg0) {
+ // Need custom matcher because order of language in the exception is not deterministic (hashmap)
+ return arg0.toString().contains("Language of file 'abc.xhtml' can not be decided as the file extension 'xhtml' is declared by several languages: ")
+ && arg0.toString().contains("web")
+ && arg0.toString().contains("xml");
+ }
+ });
+ recognizer.of(newInputFile("abc.xhtml"));
+ recognizer.stop();
+ }
+
+ @Test
+ public void solve_conflict_using_filepattern() throws Exception {
+ Languages languages = new Languages(new MockLanguage("xml", "xhtml"), new MockLanguage("web", "xhtml"));
+
+ Settings settings = new Settings();
+ settings.setProperty("sonar.xml.filePatterns", "xml/**");
+ settings.setProperty("sonar.web.filePatterns", "web/**");
+ LanguageRecognizer recognizer = new LanguageRecognizer(settings, languages);
+ recognizer.start();
+ assertThat(recognizer.of(newInputFile("xml/abc.xhtml"))).isEqualTo("xml");
+ assertThat(recognizer.of(newInputFile("web/abc.xhtml"))).isEqualTo("web");
+ recognizer.stop();
+ }
+
+ @Test
+ public void fail_if_conflicting_filepattern() throws Exception {
+ Languages languages = new Languages(new MockLanguage("abap", "abap"), new MockLanguage("cobol", "cobol"));
- recognizer = new LanguageRecognizer(newProject("abap"), languages);
+ Settings settings = new Settings();
+ settings.setProperty("sonar.abap.filePatterns", "*.abap,*.txt");
+ settings.setProperty("sonar.cobol.filePatterns", "*.cobol,*.txt");
+ LanguageRecognizer recognizer = new LanguageRecognizer(settings, languages);
recognizer.start();
- assertThat(recognizer.of(temp.newFile("abc"))).isEqualTo("abap");
- assertThat(recognizer.of(temp.newFile("abc.txt"))).isEqualTo("abap");
- assertThat(recognizer.of(temp.newFile("abc.java"))).isEqualTo("abap");
+ assertThat(recognizer.of(newInputFile("abc.abap"))).isEqualTo("abap");
+ assertThat(recognizer.of(newInputFile("abc.cobol"))).isEqualTo("cobol");
+ thrown.expect(SonarException.class);
+ thrown.expectMessage("Language of file 'abc.txt' can not be decided as the file matches patterns of both sonar.abap.filePatterns and sonar.cobol.filePatterns");
+ recognizer.of(newInputFile("abc.txt"));
recognizer.stop();
}
- private Project newProject(String language) {
- Project project = mock(Project.class);
- when(project.getLanguageKey()).thenReturn(language);
- return project;
+ private InputFile newInputFile(String path) throws IOException {
+ File basedir = temp.newFolder();
+ return new InputFileBuilder(new File(basedir, path), Charsets.UTF_8, path).build();
}
static class MockLanguage implements Language {
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
index dda95c0d131..0bc0fd04085 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
@@ -158,6 +158,10 @@ public interface CoreProperties {
*/
String PROJECT_DATE_PROPERTY = "sonar.projectDate";
+ /**
+ * @deprecated since 4.2 project are now multi-language
+ */
+ @Deprecated
String PROJECT_LANGUAGE_PROPERTY = "sonar.language";
String DYNAMIC_ANALYSIS_PROPERTY = "sonar.dynamicAnalysis";
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/resources/Languages.java b/sonar-plugin-api/src/main/java/org/sonar/api/resources/Languages.java
index b062fe1fa8b..45736dd9b9c 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/resources/Languages.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/resources/Languages.java
@@ -28,6 +28,7 @@ import org.sonar.api.ServerComponent;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -89,4 +90,12 @@ public class Languages implements BatchComponent, ServerComponent {
map.put(language.getKey(), language);
}
+ /**
+ * @since 4.2
+ */
+ public Language[] all() {
+ Collection<Language> languages = map.values();
+ return languages.toArray(new Language[languages.size()]);
+ }
+
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/resources/Project.java b/sonar-plugin-api/src/main/java/org/sonar/api/resources/Project.java
index 610fe166957..eaa18c66e51 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/resources/Project.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/resources/Project.java
@@ -229,8 +229,10 @@ public class Project extends Resource implements Component {
}
/**
- * @return the project language
+ * @return the project language when there is only one language
+ * @deprecated since 4.2
*/
+ @Deprecated
@Override
public Language getLanguage() {
return language;
@@ -242,10 +244,12 @@ public class Project extends Resource implements Component {
}
/**
- * @return the language key
+ * @return the language key or empty if no language is specified
+ * @deprecated since 4.2
*/
+ @Deprecated
public String getLanguageKey() {
- return configuration.getString("sonar.language", Java.KEY);
+ return configuration.getString(CoreProperties.PROJECT_LANGUAGE_PROPERTY, "");
}
/**