]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9477 Deprecate ProjectReactor and ProjectBuilder
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Mon, 10 Jul 2017 08:38:27 +0000 (10:38 +0200)
committerDuarte Meneses <duarte.meneses@sonarsource.com>
Tue, 11 Jul 2017 06:51:38 +0000 (08:51 +0200)
Mark Immutable classes in the Plugin API and Scanner

143 files changed:
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/LineMeasureSensor.java
sonar-core/src/main/java/org/sonar/core/metric/ScannerMetrics.java
sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/PmdBlockChunker.java
sonar-plugin-api/src/main/java/org/sonar/api/SonarRuntime.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectBuilder.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectDefinition.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectReactor.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/internal/ProjectBuilderContext.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/debt/DebtRemediationFunction.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/InputModule.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultIndexedFile.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputModule.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/FileMetadata.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/InputModuleHierarchy.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/IntArrayList.java [deleted file]
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/Metadata.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/PathPattern.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/PathPatternPredicate.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/TestInputFileBuilder.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/CharHandler.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/FileHashComputer.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/IntArrayList.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineCounter.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineHashComputer.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineOffsetCounter.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/package-info.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/measure/MetricFinder.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/ActiveRule.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/ActiveRules.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/RuleParam.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rules.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultActiveRules.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRules.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/cpd/internal/DefaultCpdTokens.java
sonar-plugin-api/src/main/java/org/sonar/api/resources/Project.java
sonar-plugin-api/src/main/java/org/sonar/api/scan/filesystem/PathResolver.java
sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/FilterableIssue.java
sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/IssueFilter.java
sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/IssueFilterChain.java
sonar-plugin-api/src/main/java/org/sonar/api/utils/WildcardPattern.java
sonar-plugin-api/src/test/java/org/sonar/api/batch/fs/internal/DefaultInputFileTest.java
sonar-plugin-api/src/test/java/org/sonar/api/batch/fs/internal/IntArrayListTest.java [deleted file]
sonar-plugin-api/src/test/java/org/sonar/api/batch/fs/internal/PathPatternTest.java
sonar-plugin-api/src/test/java/org/sonar/api/batch/fs/internal/charhandler/IntArrayListTest.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/DefaultFileLinesContext.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/ProjectAnalysisInfo.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/analysis/AnalysisTempFolderProvider.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/analysis/DefaultAnalysisMode.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/GlobalMode.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerExtensionDictionnary.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/cpd/CpdExecutor.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/cpd/CpdSettings.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/cpd/index/SonarCpdBlockIndex.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/index/DefaultIndex.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DefaultFilterableIssue.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DefaultIssueFilterChain.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssueFilters.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ModuleIssues.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ignore/EnforceIssuesFilter.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ignore/pattern/IssuePattern.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ignore/scanner/IssueExclusionsLoader.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ignore/scanner/IssueExclusionsRegexpScanner.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/tracking/ServerIssueRepository.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/AbstractPhaseExecutor.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/InitializersExecutor.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/IssuesPhaseExecutor.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/PostJobsExecutor.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/ProjectAnalysisEvent.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/PublishPhaseExecutor.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/phases/SensorsExecutor.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/AnalysisContextReportPublisher.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ComponentsPublisher.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ReportPublisher.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/ContextPropertiesCache.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/ProjectRepositories.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/language/DefaultLanguagesRepository.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/language/Language.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/language/LanguagesRepository.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/ModuleQProfiles.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/QProfile.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/RuleFinderCompatibility.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/DefaultInputModuleHierarchy.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ImmutableProjectReactor.java [deleted file]
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ImmutableProjectReactorProvider.java [deleted file]
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/InputModuleHierarchyProvider.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleIndexer.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleSettingsProvider.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectLock.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectReactorValidator.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/WorkDirectoryCleaner.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/BatchIdGenerator.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ExclusionFilters.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/InputComponentStore.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/InputComponentStoreProvider.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/InputFileBuilder.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/LanguageDetection.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ModuleFileSystemInitializer.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/StatusDetection.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/measure/DefaultMetricFinder.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/report/RuleNameProvider.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmConfiguration.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/coverage/CoverageExclusions.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/storage/Storage.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/analysis/AnalysisTempFolderProviderTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/cpd/CpdExecutorTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/cpd/CpdSettingsTest.java [new file with mode: 0644]
sonar-scanner-engine/src/test/java/org/sonar/scanner/index/DefaultIndexTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/ignore/EnforceIssuesFilterTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/tracking/SourceHashHolderTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/SensorsExecutorTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/postjob/DefaultPostJobContextTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/profiling/PhasesSumUpTimeProfilerTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/AnalysisContextReportPublisherTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/CoveragePublisherTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MeasuresPublisherTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MetadataPublisherTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ReportPublisherTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/SourcePublisherTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/QProfileTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/QProfileVerifierTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/RulesProfileProviderTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/DefaultInputModuleHierarchyTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ModuleIndexerTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectLockTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/ProjectReactorValidatorTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/WorkDirectoryCleanerTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/ExclusionFiltersTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/InputComponentStoreTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/LanguageDetectionTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/ModuleFileSystemInitializerTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/ModuleInputComponentStoreTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/report/JSONReportTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/coverage/CoverageExclusionsTest.java
sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java
sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java

index fb8f267eca20391c4f70ab0b8099b4ab95747b0c..7d4c2bc0273ed6bb9f87eb45b5e1867b94572716 100644 (file)
@@ -26,7 +26,6 @@ import java.util.Map;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.batch.fs.InputFile;
-import org.sonar.api.batch.measure.MetricFinder;
 import org.sonar.api.batch.sensor.Sensor;
 import org.sonar.api.batch.sensor.SensorContext;
 import org.sonar.api.batch.sensor.SensorDescriptor;
@@ -46,13 +45,10 @@ public class LineMeasureSensor implements Sensor {
 
   private static final String MEASURES_EXTENSION = ".linemeasures";
 
-  private MetricFinder metricFinder;
-
   private FileLinesContextFactory contextFactory;
 
   public LineMeasureSensor(FileLinesContextFactory contextFactory) {
     this.contextFactory = contextFactory;
-    this.metricFinder = metricFinder;
   }
 
   private void processFileMeasures(InputFile inputFile, SensorContext context) {
index 6406336c580abf408a80298d7c57e1e04c709bf6..3052cefcffb1233a0fa4f3d4a5214d3d5117384f 100644 (file)
@@ -24,6 +24,9 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Stream;
+
+import javax.annotation.concurrent.Immutable;
+
 import org.sonar.api.batch.ScannerSide;
 import org.sonar.api.ce.ComputeEngineSide;
 import org.sonar.api.measures.Metric;
@@ -70,6 +73,7 @@ import static org.sonar.core.util.stream.MoreCollectors.toSet;
  * <p/>
  * Scanners should not send other metrics, and the Compute Engine should not allow other metrics.
  */
+@Immutable
 @ComputeEngineSide
 @ScannerSide
 public class ScannerMetrics {
index f992b942982833d2714496c0328c5b6268f47a68..4438e197abe282bf417c4b624234fc1f913e11af 100644 (file)
@@ -21,6 +21,9 @@ package org.sonar.duplications.internal.pmd;
 
 import java.util.ArrayList;
 import java.util.List;
+
+import javax.annotation.concurrent.Immutable;
+
 import org.sonar.duplications.block.Block;
 import org.sonar.duplications.block.ByteArray;
 
@@ -29,6 +32,7 @@ import org.sonar.duplications.block.ByteArray;
  * works with {@link TokensLine},
  * sets {@link Block#getStartUnit() startUnit} and {@link Block#getEndUnit() endUnit} - indexes of first and last token for this block.
  */
+@Immutable
 public class PmdBlockChunker {
 
   private static final long PRIME_BASE = 31;
index bea756e4bc2bb3d67ef150de09c43df1966c649c..de0cfa41cee5d01692d62c59412cfaee45295c26 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.api;
 
+import javax.annotation.concurrent.Immutable;
+
 import org.sonar.api.batch.ScannerSide;
 import org.sonar.api.batch.sensor.Sensor;
 import org.sonar.api.batch.sensor.SensorContext;
@@ -141,6 +143,7 @@ import org.sonarsource.api.sonarlint.SonarLintSide;
 @ServerSide
 @ComputeEngineSide
 @SonarLintSide
+@Immutable
 public interface SonarRuntime {
 
   /**
index eac17aefa31554b7a9cb6f7c5ba2a0a94592aff8..7d557241ee08a9787f65d32b4b105aa5001cf140 100644 (file)
@@ -33,17 +33,20 @@ import org.sonar.api.batch.ScannerSide;
  *   <li>Change project metadata like description or source directories.</li>
  * </ul>
  *
+ * @deprecated since 6.5. It won't be possible to manipulate the project's structure.
  * @since 2.9
  */
 @ScannerSide
 @InstantiationStrategy(InstantiationStrategy.PER_BATCH)
 @ExtensionPoint
+@Deprecated
 public abstract class ProjectBuilder {
 
   /**
    * Plugins can use the implementation {@link org.sonar.api.batch.bootstrap.internal.ProjectBuilderContext}
    * for their unit tests.
    */
+  @Deprecated
   public interface Context {
     ProjectReactor projectReactor();
   }
index 19cc04bdaaf10898fc2641b590e65a05b5fde340..c3c75506131e8d677b2b0c505bd481895cc1f0b6 100644 (file)
@@ -36,6 +36,8 @@ import org.sonar.api.CoreProperties;
  * {@link org.sonar.api.batch.bootstrap.ProjectBuilder extension point} and must not be used
  * by other standard extensions.
  *
+ * Since 6.5, plugins should no longer manipulate the project's structure.
+ *
  * @since 2.9
  */
 public class ProjectDefinition {
index 46ea9aba68a7bd172dd123fa11af8dad9100f49b..8aba5c679a249fcf95d71eff5743b8f2554e34aa 100644 (file)
@@ -26,8 +26,11 @@ import java.util.List;
 
 /**
  * Mutable project definitions that can be modified by {@link ProjectBuilder} extensions.
+ * 
+ * @deprecated since 6.5 plugins should no longer modify the project's structure
  * @since 2.9
  */
+@Deprecated
 @ScannerSide
 public class ProjectReactor implements ProjectKey {
 
@@ -41,7 +44,7 @@ public class ProjectReactor implements ProjectKey {
   }
 
   public List<ProjectDefinition> getProjects() {
-    return collectProjects(root, new ArrayList<ProjectDefinition>());
+    return collectProjects(root, new ArrayList<>());
   }
 
   /**
index 6d2024037baf2e0b8c997a4ebf65169a724c21b0..b3093aa70113f3b2eafdefef900e9164389249ac 100644 (file)
@@ -27,8 +27,10 @@ import org.sonar.api.batch.bootstrap.ProjectReactor;
  * Context that is passed to {@link org.sonar.api.batch.bootstrap.ProjectBuilder} as parameter.
  * Important - plugins must use this class only for unit test needs.
  *
+ * @deprecated since 6.5
  * @since 3.7
  */
+@Deprecated
 public class ProjectBuilderContext implements ProjectBuilder.Context {
 
   private final ProjectReactor reactor;
index a0b2e24404d118334dc8e613ed614716c56bc4f1..cbfc136877905529cfd9a4470992b4beda8cc671 100644 (file)
@@ -22,12 +22,14 @@ package org.sonar.api.batch.debt;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.api.utils.Duration;
+import javax.annotation.concurrent.Immutable;
 
 /**
  * @since 4.3
  * @deprecated since 6.5 debt model will soon be unavailable on batch side
  */
 @Deprecated
+@Immutable
 public class DebtRemediationFunction {
 
   public enum Type {
index 373ba890055a7850db885b7226818749d9448dbf..c1aaf9fa36f275b1d745bedb598f8547e8bf5611 100644 (file)
@@ -19,6 +19,9 @@
  */
 package org.sonar.api.batch.fs;
 
+
+import javax.annotation.concurrent.Immutable;
+
 import org.sonar.api.batch.sensor.SensorContext;
 
 /**
@@ -26,5 +29,6 @@ import org.sonar.api.batch.sensor.SensorContext;
  *
  * @since 5.2
  */
+@Immutable
 public interface InputModule extends InputComponent {
 }
index d526e7f57cc128ef77566b78bac5ab3b3d3db969..b3a0dbbe9eec1d298c6817361fcb9a01b6b925a8 100644 (file)
@@ -24,8 +24,11 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
+
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
 import org.sonar.api.batch.fs.IndexedFile;
 import org.sonar.api.batch.fs.InputFile.Type;
 import org.sonar.api.utils.PathUtils;
@@ -33,35 +36,34 @@ import org.sonar.api.utils.PathUtils;
 /**
  * @since 6.3
  */
+@Immutable
 public class DefaultIndexedFile extends DefaultInputComponent implements IndexedFile {
   private final String relativePath;
   private final String moduleKey;
   private final Path moduleBaseDir;
-  private String language;
+  private final String language;
   private final Type type;
+  private final Path path;
 
   /**
    * Testing purposes only!
    */
-  public DefaultIndexedFile(String moduleKey, Path moduleBaseDir, String relativePath) {
-    this(moduleKey, moduleBaseDir, relativePath, TestInputFileBuilder.nextBatchId());
+  public DefaultIndexedFile(String moduleKey, Path moduleBaseDir, String relativePath, @Nullable String language) {
+    this(moduleKey, moduleBaseDir, relativePath, language, TestInputFileBuilder.nextBatchId());
   }
 
-  public DefaultIndexedFile(String moduleKey, Path moduleBaseDir, String relativePath, int batchId) {
-    this(moduleKey, moduleBaseDir, relativePath, Type.MAIN, batchId);
+  public DefaultIndexedFile(String moduleKey, Path moduleBaseDir, String relativePath, @Nullable String language, int batchId) {
+    this(moduleKey, moduleBaseDir, relativePath, Type.MAIN, language, batchId);
   }
 
-  public DefaultIndexedFile(String moduleKey, Path moduleBaseDir, String relativePath, Type type, int batchId) {
+  public DefaultIndexedFile(String moduleKey, Path moduleBaseDir, String relativePath, Type type, @Nullable String language, int batchId) {
     super(batchId);
     this.moduleKey = moduleKey;
     this.relativePath = PathUtils.sanitize(relativePath);
     this.moduleBaseDir = moduleBaseDir.normalize();
     this.type = type;
-  }
-
-  public DefaultIndexedFile setLanguage(@Nullable String language) {
     this.language = language;
-    return this;
+    this.path = this.moduleBaseDir.resolve(this.relativePath);
   }
 
   @Override
@@ -81,7 +83,7 @@ public class DefaultIndexedFile extends DefaultInputComponent implements Indexed
 
   @Override
   public Path path() {
-    return moduleBaseDir.resolve(relativePath);
+    return path;
   }
 
   @Override
index ff881a6f75dea7bff8b7707177943aaf3af7a9b5..b3ea8263c0d723fd95d3b161015e9587a205449c 100644 (file)
@@ -47,12 +47,13 @@ public class DefaultInputFile extends DefaultInputComponent implements InputFile
   private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
 
   private final DefaultIndexedFile indexedFile;
+  private final String contents;
   private final Consumer<DefaultInputFile> metadataGenerator;
+
   private Status status;
   private Charset charset;
   private Metadata metadata;
   private boolean publish;
-  private String contents;
 
   public DefaultInputFile(DefaultIndexedFile indexedFile, Consumer<DefaultInputFile> metadataGenerator) {
     this(indexedFile, metadataGenerator, null);
index ac8eced2bbd2c382ac77714882397fa5f5381e3e..1165e54d4945d068d7c9f646ded7ba9ef9ec1078 100644 (file)
  */
 package org.sonar.api.batch.fs.internal;
 
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.Immutable;
+
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.batch.fs.InputModule;
 
 /**
  * @since 5.2
  */
+@Immutable
 public class DefaultInputModule extends DefaultInputComponent implements InputModule {
+  private final File baseDir;
+  private final File workDir;
+  private final String name;
+  private final String version;
+  private final String originalName;
+  private final String originalVersion;
+  private final String description;
+  private final String keyWithBranch;
+  private final String branch;
+  private final List<String> sources;
+  private final List<String> tests;
+  private final Map<String, String> properties;
 
   private final String moduleKey;
   private final ProjectDefinition definition;
@@ -36,9 +59,29 @@ public class DefaultInputModule extends DefaultInputComponent implements InputMo
   public DefaultInputModule(String moduleKey) {
     this(ProjectDefinition.create().setKey(moduleKey), TestInputFileBuilder.nextBatchId());
   }
+  
+  /**
+   * For testing only!
+   */
+  public DefaultInputModule(ProjectDefinition definition) {
+    this(definition, TestInputFileBuilder.nextBatchId());
+  }
 
   public DefaultInputModule(ProjectDefinition definition, int batchId) {
     super(batchId);
+    this.baseDir = definition.getBaseDir();
+    this.workDir = definition.getWorkDir();
+    this.name = definition.getName();
+    this.originalName = definition.getOriginalName();
+    this.version = definition.getVersion();
+    this.originalVersion = definition.getOriginalVersion();
+    this.description = definition.getDescription();
+    this.keyWithBranch = definition.getKeyWithBranch();
+    this.branch = definition.getBranch();
+    this.sources = Collections.unmodifiableList(new ArrayList<>(definition.sources()));
+    this.tests = Collections.unmodifiableList(new ArrayList<>(definition.tests()));
+    this.properties = Collections.unmodifiableMap(new HashMap<>(definition.properties()));
+
     this.definition = definition;
     this.moduleKey = definition.getKey();
   }
@@ -59,5 +102,59 @@ public class DefaultInputModule extends DefaultInputComponent implements InputMo
   public ProjectDefinition definition() {
     return definition;
   }
+  
+  public File getBaseDir() {
+    return baseDir;
+  }
+
+  public File getWorkDir() {
+    return workDir;
+  }
+
+  public String getKeyWithBranch() {
+    return keyWithBranch;
+  }
+
+  @CheckForNull
+  public String getBranch() {
+    return branch;
+  }
+
+  public Map<String, String> properties() {
+    return properties;
+  }
+
+  @CheckForNull
+  public String getOriginalVersion() {
+    return originalVersion;
+  }
+
+  public String getVersion() {
+    return version;
+  }
+
+  @CheckForNull
+  public String getOriginalName() {
+    return originalName;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  /**
+   * @return Source files and folders.
+   */
+  public List<String> sources() {
+    return sources;
+  }
+
+  public List<String> tests() {
+    return tests;
+  }
 
 }
index f444c26bd0db2e501cf277d32fb8cdb4cf538a23..4a96ce615918ff436d683b3d4446d3975c16a638 100644 (file)
 package org.sonar.api.batch.fs.internal;
 
 import java.io.BufferedReader;
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.CharacterCodingException;
 import java.nio.charset.Charset;
-import java.nio.charset.CharsetEncoder;
-import java.nio.charset.CodingErrorAction;
 import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
 
-import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
 
-import org.apache.commons.codec.binary.Hex;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.ScannerSide;
 import org.sonar.api.batch.fs.InputFile;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
+import org.sonar.api.batch.fs.internal.charhandler.CharHandler;
+import org.sonar.api.batch.fs.internal.charhandler.FileHashComputer;
+import org.sonar.api.batch.fs.internal.charhandler.LineCounter;
+import org.sonar.api.batch.fs.internal.charhandler.LineHashComputer;
+import org.sonar.api.batch.fs.internal.charhandler.LineOffsetCounter;
 
 /**
  * Computes hash of files. Ends of Lines are ignored, so files with
  * same content but different EOL encoding have the same hash.
  */
 @ScannerSide
+@Immutable
 public class FileMetadata {
-
-  private static final Logger LOG = Loggers.get(FileMetadata.class);
-
   private static final char LINE_FEED = '\n';
   private static final char CARRIAGE_RETURN = '\r';
 
-  public abstract static class CharHandler {
-
-    protected void handleAll(char c) {
-    }
-
-    protected void handleIgnoreEoL(char c) {
-    }
-
-    protected void newLine() {
-    }
-
-    protected void eof() {
-    }
-  }
-
-  private static class LineCounter extends CharHandler {
-    private int lines = 1;
-    private int nonBlankLines = 0;
-    private boolean blankLine = true;
-    boolean alreadyLoggedInvalidCharacter = false;
-    private final String filePath;
-    private final Charset encoding;
-
-    LineCounter(String filePath, Charset encoding) {
-      this.filePath = filePath;
-      this.encoding = encoding;
-    }
-
-    @Override
-    protected void handleAll(char c) {
-      if (!alreadyLoggedInvalidCharacter && c == '\ufffd') {
-        LOG.warn("Invalid character encountered in file {} at line {} for encoding {}. Please fix file content or configure the encoding to be used using property '{}'.", filePath,
-          lines, encoding, CoreProperties.ENCODING_PROPERTY);
-        alreadyLoggedInvalidCharacter = true;
-      }
-    }
-
-    @Override
-    protected void newLine() {
-      lines++;
-      if (!blankLine) {
-        nonBlankLines++;
-      }
-      blankLine = true;
-    }
-
-    @Override
-    protected void handleIgnoreEoL(char c) {
-      if (!Character.isWhitespace(c)) {
-        blankLine = false;
-      }
-    }
-
-    @Override
-    protected void eof() {
-      if (!blankLine) {
-        nonBlankLines++;
-      }
-    }
-
-    public int lines() {
-      return lines;
-    }
-
-    public int nonBlankLines() {
-      return nonBlankLines;
-    }
-
-  }
-
-  private static class FileHashComputer extends CharHandler {
-    private MessageDigest globalMd5Digest = DigestUtils.getMd5Digest();
-    private StringBuilder sb = new StringBuilder();
-    private final CharsetEncoder encoder;
-    private final String filePath;
-
-    public FileHashComputer(String filePath) {
-      encoder = StandardCharsets.UTF_8.newEncoder()
-        .onMalformedInput(CodingErrorAction.REPLACE)
-        .onUnmappableCharacter(CodingErrorAction.REPLACE);
-      this.filePath = filePath;
-    }
-
-    @Override
-    protected void handleIgnoreEoL(char c) {
-      sb.append(c);
-    }
-
-    @Override
-    protected void newLine() {
-      sb.append(LINE_FEED);
-      processBuffer();
-      sb.setLength(0);
-    }
-
-    @Override
-    protected void eof() {
-      if (sb.length() > 0) {
-        processBuffer();
-      }
-    }
-
-    private void processBuffer() {
-      try {
-        if (sb.length() > 0) {
-          ByteBuffer encoded = encoder.encode(CharBuffer.wrap(sb));
-          globalMd5Digest.update(encoded.array(), 0, encoded.limit());
-        }
-      } catch (CharacterCodingException e) {
-        throw new IllegalStateException("Error encoding line hash in file: " + filePath, e);
-      }
-    }
-
-    @CheckForNull
-    public String getHash() {
-      return Hex.encodeHexString(globalMd5Digest.digest());
-    }
-  }
-
-  private static class LineHashComputer extends CharHandler {
-    private final MessageDigest lineMd5Digest = DigestUtils.getMd5Digest();
-    private final CharsetEncoder encoder;
-    private final StringBuilder sb = new StringBuilder();
-    private final LineHashConsumer consumer;
-    private final File file;
-    private int line = 1;
-
-    public LineHashComputer(LineHashConsumer consumer, File f) {
-      this.consumer = consumer;
-      this.file = f;
-      this.encoder = StandardCharsets.UTF_8.newEncoder()
-        .onMalformedInput(CodingErrorAction.REPLACE)
-        .onUnmappableCharacter(CodingErrorAction.REPLACE);
-    }
-
-    @Override
-    protected void handleIgnoreEoL(char c) {
-      if (!Character.isWhitespace(c)) {
-        sb.append(c);
-      }
-    }
-
-    @Override
-    protected void newLine() {
-      processBuffer();
-      sb.setLength(0);
-      line++;
-    }
-
-    @Override
-    protected void eof() {
-      if (this.line > 0) {
-        processBuffer();
-      }
-    }
-
-    private void processBuffer() {
-      try {
-        if (sb.length() > 0) {
-          ByteBuffer encoded = encoder.encode(CharBuffer.wrap(sb));
-          lineMd5Digest.update(encoded.array(), 0, encoded.limit());
-          consumer.consume(line, lineMd5Digest.digest());
-        }
-      } catch (CharacterCodingException e) {
-        throw new IllegalStateException("Error encoding line hash in file: " + file.getAbsolutePath(), e);
-      }
-    }
-  }
-
-  private static class LineOffsetCounter extends CharHandler {
-    private long currentOriginalOffset = 0;
-    private IntArrayList originalLineOffsets = new IntArrayList();
-    private long lastValidOffset = 0;
-
-    public LineOffsetCounter() {
-      originalLineOffsets.add(0);
-    }
-
-    @Override
-    protected void handleAll(char c) {
-      currentOriginalOffset++;
-    }
-
-    @Override
-    protected void newLine() {
-      if (currentOriginalOffset > Integer.MAX_VALUE) {
-        throw new IllegalStateException("File is too big: " + currentOriginalOffset);
-      }
-      originalLineOffsets.add((int) currentOriginalOffset);
-    }
-
-    @Override
-    protected void eof() {
-      lastValidOffset = currentOriginalOffset;
-    }
-
-    public int[] getOriginalLineOffsets() {
-      return originalLineOffsets.trimAndGet();
-    }
-
-    public int getLastValidOffset() {
-      if (lastValidOffset > Integer.MAX_VALUE) {
-        throw new IllegalStateException("File is too big: " + lastValidOffset);
-      }
-      return (int) lastValidOffset;
-    }
-
-  }
-
   /**
    * Compute hash of a file ignoring line ends differences.
    * Maximum performance is needed.
index 468a052f5077b8a6722cd729ef495e6c2a3b9fce..b258fb04dd222226a95f49f7ec17a4864e5cb9bf 100644 (file)
@@ -22,10 +22,11 @@ package org.sonar.api.batch.fs.internal;
 import java.util.Collection;
 
 import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.Immutable;
 
 import org.sonar.api.batch.fs.InputModule;
-import org.sonar.api.batch.fs.internal.DefaultInputModule;
 
+@Immutable
 public interface InputModuleHierarchy {
   DefaultInputModule root();
   
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/IntArrayList.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/IntArrayList.java
deleted file mode 100644 (file)
index 913c0a2..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.api.batch.fs.internal;
-
-import java.util.Arrays;
-import java.util.Collection;
-
-/**
- * Specialization of {@link java.util.ArrayList} to create a list of int (only append elements) and then produce an int[].
- */
-class IntArrayList {
-
-  /**
-   * Default initial capacity.
-   */
-  private static final int DEFAULT_CAPACITY = 10;
-
-  /**
-   * Shared empty array instance used for default sized empty instances. We
-   * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
-   * first element is added.
-   */
-  private static final int[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
-
-  /**
-   * The array buffer into which the elements of the ArrayList are stored.
-   * The capacity of the IntArrayList is the length of this array buffer. Any
-   * empty IntArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
-   * will be expanded to DEFAULT_CAPACITY when the first element is added.
-   */
-  private int[] elementData;
-
-  /**
-   * The size of the IntArrayList (the number of elements it contains).
-   */
-  private int size;
-
-  /**
-   * Constructs an empty list with an initial capacity of ten.
-   */
-  public IntArrayList() {
-    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
-  }
-
-  /**
-   * Trims the capacity of this <tt>IntArrayList</tt> instance to be the
-   * list's current size and return the internal array. An application can use this operation to minimize
-   * the storage of an <tt>IntArrayList</tt> instance.
-   */
-  public int[] trimAndGet() {
-    if (size < elementData.length) {
-      elementData = Arrays.copyOf(elementData, size);
-    }
-    return elementData;
-  }
-
-  private void ensureCapacityInternal(int minCapacity) {
-    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
-      minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
-    }
-
-    ensureExplicitCapacity(minCapacity);
-  }
-
-  private void ensureExplicitCapacity(int minCapacity) {
-    if (minCapacity - elementData.length > 0) {
-      grow(minCapacity);
-    }
-  }
-
-  /**
-   * Increases the capacity to ensure that it can hold at least the
-   * number of elements specified by the minimum capacity argument.
-   *
-   * @param minCapacity the desired minimum capacity
-   */
-  private void grow(int minCapacity) {
-    int oldCapacity = elementData.length;
-    int newCapacity = oldCapacity + (oldCapacity >> 1);
-    if (newCapacity - minCapacity < 0) {
-      newCapacity = minCapacity;
-    }
-    elementData = Arrays.copyOf(elementData, newCapacity);
-  }
-
-  /**
-   * Appends the specified element to the end of this list.
-   *
-   * @param e element to be appended to this list
-   * @return <tt>true</tt> (as specified by {@link Collection#add})
-   */
-  public boolean add(int e) {
-    ensureCapacityInternal(size + 1);
-    elementData[size] = e;
-    size++;
-    return true;
-  }
-
-}
index 71d79d1007aab169704f64d0415c941ef5940316..9323c73b0621060d3e6ca080583289b81f1b241a 100644 (file)
  */
 package org.sonar.api.batch.fs.internal;
 
+import java.util.Arrays;
+
+import javax.annotation.concurrent.Immutable;
+
+@Immutable
 public class Metadata {
   private final int lines;
   private final int nonBlankLines;
@@ -30,7 +35,7 @@ public class Metadata {
     this.lines = lines;
     this.nonBlankLines = nonBlankLines;
     this.hash = hash;
-    this.originalLineOffsets = originalLineOffsets;
+    this.originalLineOffsets = Arrays.copyOf(originalLineOffsets, originalLineOffsets.length);
     this.lastValidOffset = lastValidOffset;
   }
 
index 556ce3f5de04a7b7e9e130a2c8b6dcca67c3e504..5ec259f0021a03b5f165156d348578c5d0bb9d60 100644 (file)
  */
 package org.sonar.api.batch.fs.internal;
 
+import javax.annotation.concurrent.ThreadSafe;
+
 import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.lang.StringUtils;
-import org.sonar.api.batch.fs.IndexedFile;
 import org.sonar.api.utils.WildcardPattern;
 
+@ThreadSafe
 public abstract class PathPattern {
 
   final WildcardPattern pattern;
@@ -32,9 +34,9 @@ public abstract class PathPattern {
     this.pattern = WildcardPattern.create(pattern);
   }
 
-  public abstract boolean match(IndexedFile inputFile);
+  public abstract boolean match(String absolutePath, String relativePath);
 
-  public abstract boolean match(IndexedFile inputFile, boolean caseSensitiveFileExtension);
+  public abstract boolean match(String absolutePath, String relativePath, boolean caseSensitiveFileExtension);
 
   public static PathPattern create(String s) {
     String trimmed = StringUtils.trim(s);
@@ -58,15 +60,15 @@ public abstract class PathPattern {
     }
 
     @Override
-    public boolean match(IndexedFile inputFile) {
-      return match(inputFile, true);
+    public boolean match(String absolutePath, String relativePath) {
+      return match(absolutePath, relativePath, true);
     }
 
     @Override
-    public boolean match(IndexedFile inputFile, boolean caseSensitiveFileExtension) {
-      String path = inputFile.absolutePath();
+    public boolean match(String absolutePath, String relativePath, boolean caseSensitiveFileExtension) {
+      String path = absolutePath;
       if (!caseSensitiveFileExtension) {
-        String extension = sanitizeExtension(FilenameUtils.getExtension(inputFile.file().getName()));
+        String extension = sanitizeExtension(FilenameUtils.getExtension(relativePath));
         if (StringUtils.isNotBlank(extension)) {
           path = StringUtils.removeEndIgnoreCase(path, extension);
           path = path + extension;
@@ -90,15 +92,15 @@ public abstract class PathPattern {
     }
 
     @Override
-    public boolean match(IndexedFile inputFile) {
-      return match(inputFile, true);
+    public boolean match(String absolutePath, String relativePath) {
+      return match(absolutePath, relativePath, true);
     }
 
     @Override
-    public boolean match(IndexedFile inputFile, boolean caseSensitiveFileExtension) {
-      String path = inputFile.relativePath();
+    public boolean match(String absolutePath, String relativePath, boolean caseSensitiveFileExtension) {
+      String path = relativePath;
       if (!caseSensitiveFileExtension) {
-        String extension = sanitizeExtension(FilenameUtils.getExtension(inputFile.file().getName()));
+        String extension = sanitizeExtension(FilenameUtils.getExtension(relativePath));
         if (StringUtils.isNotBlank(extension)) {
           path = StringUtils.removeEndIgnoreCase(path, extension);
           path = path + extension;
index 400fd64f37b1d3562bdd3ccbf06a302d2e40cd19..e6b4a9246e38ddb8b935915e5a38c9946cf11f82 100644 (file)
@@ -34,7 +34,7 @@ class PathPatternPredicate extends AbstractFilePredicate {
 
   @Override
   public boolean apply(InputFile f) {
-    return pattern.match(f);
+    return pattern.match(f.absolutePath(), f.relativePath());
   }
 
 }
index 6fc584fdfbc94c595de717b32819f88b46d2a70b..fa86a9e17e6864d740dc4e18f7d5b9763065fd20 100644 (file)
@@ -66,7 +66,7 @@ public class TestInputFileBuilder {
   private int lastValidOffset = -1;
   private String hash;
   private int nonBlankLines;
-  private int[] originalLineOffsets;
+  private int[] originalLineOffsets = new int[0];
   private boolean publish = true;
   private String contents;
 
@@ -194,8 +194,7 @@ public class TestInputFileBuilder {
   }
 
   public DefaultInputFile build() {
-    DefaultIndexedFile indexedFile = new DefaultIndexedFile(moduleKey, moduleBaseDir, relativePath, type, id);
-    indexedFile.setLanguage(language);
+    DefaultIndexedFile indexedFile = new DefaultIndexedFile(moduleKey, moduleBaseDir, relativePath, type, language, id);
     DefaultInputFile inputFile = new DefaultInputFile(indexedFile,
       f -> f.setMetadata(new Metadata(lines, nonBlankLines, hash, originalLineOffsets, lastValidOffset)),
       contents);
@@ -206,8 +205,11 @@ public class TestInputFileBuilder {
   }
 
   public static DefaultInputModule newDefaultInputModule(String moduleKey, File baseDir) {
-    ProjectDefinition definition = ProjectDefinition.create().setKey(moduleKey);
-    definition.setBaseDir(baseDir);
-    return new DefaultInputModule(definition, TestInputFileBuilder.nextBatchId());
+    ProjectDefinition definition = ProjectDefinition.create().setKey(moduleKey).setBaseDir(baseDir);
+    return newDefaultInputModule(definition);
+  }
+
+  public static DefaultInputModule newDefaultInputModule(ProjectDefinition projectDefinition) {
+    return new DefaultInputModule(projectDefinition, TestInputFileBuilder.nextBatchId());
   }
 }
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/CharHandler.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/CharHandler.java
new file mode 100644 (file)
index 0000000..7694132
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.batch.fs.internal.charhandler;
+
+public abstract class CharHandler {
+
+  public void handleAll(char c) {
+  }
+
+  public void handleIgnoreEoL(char c) {
+  }
+
+  public void newLine() {
+  }
+
+  public void eof() {
+  }
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/FileHashComputer.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/FileHashComputer.java
new file mode 100644 (file)
index 0000000..d1bfa79
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.batch.fs.internal.charhandler;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+
+import javax.annotation.CheckForNull;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
+
+public class FileHashComputer extends CharHandler {
+  private static final char LINE_FEED = '\n';
+
+  
+  private MessageDigest globalMd5Digest = DigestUtils.getMd5Digest();
+  private StringBuilder sb = new StringBuilder();
+  private final CharsetEncoder encoder;
+  private final String filePath;
+
+  public FileHashComputer(String filePath) {
+    encoder = StandardCharsets.UTF_8.newEncoder()
+      .onMalformedInput(CodingErrorAction.REPLACE)
+      .onUnmappableCharacter(CodingErrorAction.REPLACE);
+    this.filePath = filePath;
+  }
+
+  @Override
+  public void handleIgnoreEoL(char c) {
+    sb.append(c);
+  }
+
+  @Override
+  public void newLine() {
+    sb.append(LINE_FEED);
+    processBuffer();
+    sb.setLength(0);
+  }
+
+  @Override
+  public void eof() {
+    if (sb.length() > 0) {
+      processBuffer();
+    }
+  }
+
+  private void processBuffer() {
+    try {
+      if (sb.length() > 0) {
+        ByteBuffer encoded = encoder.encode(CharBuffer.wrap(sb));
+        globalMd5Digest.update(encoded.array(), 0, encoded.limit());
+      }
+    } catch (CharacterCodingException e) {
+      throw new IllegalStateException("Error encoding line hash in file: " + filePath, e);
+    }
+  }
+
+  @CheckForNull
+  public String getHash() {
+    return Hex.encodeHexString(globalMd5Digest.digest());
+  }
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/IntArrayList.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/IntArrayList.java
new file mode 100644 (file)
index 0000000..2bdcfb6
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.batch.fs.internal.charhandler;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Specialization of {@link java.util.ArrayList} to create a list of int (only append elements) and then produce an int[].
+ */
+class IntArrayList {
+
+  /**
+   * Default initial capacity.
+   */
+  private static final int DEFAULT_CAPACITY = 10;
+
+  /**
+   * Shared empty array instance used for default sized empty instances. We
+   * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
+   * first element is added.
+   */
+  private static final int[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
+
+  /**
+   * The array buffer into which the elements of the ArrayList are stored.
+   * The capacity of the IntArrayList is the length of this array buffer. Any
+   * empty IntArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
+   * will be expanded to DEFAULT_CAPACITY when the first element is added.
+   */
+  private int[] elementData;
+
+  /**
+   * The size of the IntArrayList (the number of elements it contains).
+   */
+  private int size;
+
+  /**
+   * Constructs an empty list with an initial capacity of ten.
+   */
+  public IntArrayList() {
+    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
+  }
+
+  /**
+   * Trims the capacity of this <tt>IntArrayList</tt> instance to be the
+   * list's current size and return the internal array. An application can use this operation to minimize
+   * the storage of an <tt>IntArrayList</tt> instance.
+   */
+  public int[] trimAndGet() {
+    if (size < elementData.length) {
+      elementData = Arrays.copyOf(elementData, size);
+    }
+    return elementData;
+  }
+
+  private void ensureCapacityInternal(int minCapacity) {
+    int capacity = minCapacity;
+    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
+      capacity = Math.max(DEFAULT_CAPACITY, minCapacity);
+    }
+
+    ensureExplicitCapacity(capacity);
+  }
+
+  private void ensureExplicitCapacity(int minCapacity) {
+    if (minCapacity - elementData.length > 0) {
+      grow(minCapacity);
+    }
+  }
+
+  /**
+   * Increases the capacity to ensure that it can hold at least the
+   * number of elements specified by the minimum capacity argument.
+   *
+   * @param minCapacity the desired minimum capacity
+   */
+  private void grow(int minCapacity) {
+    int oldCapacity = elementData.length;
+    int newCapacity = oldCapacity + (oldCapacity >> 1);
+    if (newCapacity - minCapacity < 0) {
+      newCapacity = minCapacity;
+    }
+    elementData = Arrays.copyOf(elementData, newCapacity);
+  }
+
+  /**
+   * Appends the specified element to the end of this list.
+   *
+   * @param e element to be appended to this list
+   * @return <tt>true</tt> (as specified by {@link Collection#add})
+   */
+  public boolean add(int e) {
+    ensureCapacityInternal(size + 1);
+    elementData[size] = e;
+    size++;
+    return true;
+  }
+
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineCounter.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineCounter.java
new file mode 100644 (file)
index 0000000..c17a867
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.batch.fs.internal.charhandler;
+
+import java.nio.charset.Charset;
+
+import org.sonar.api.CoreProperties;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+public class LineCounter extends CharHandler {
+  private static final Logger LOG = Loggers.get(LineCounter.class);
+    
+  private int lines = 1;
+  private int nonBlankLines = 0;
+  private boolean blankLine = true;
+  boolean alreadyLoggedInvalidCharacter = false;
+  private final String filePath;
+  private final Charset encoding;
+
+  public LineCounter(String filePath, Charset encoding) {
+    this.filePath = filePath;
+    this.encoding = encoding;
+  }
+
+  @Override
+  public void handleAll(char c) {
+    if (!alreadyLoggedInvalidCharacter && c == '\ufffd') {
+      LOG.warn("Invalid character encountered in file {} at line {} for encoding {}. Please fix file content or configure the encoding to be used using property '{}'.", filePath,
+        lines, encoding, CoreProperties.ENCODING_PROPERTY);
+      alreadyLoggedInvalidCharacter = true;
+    }
+  }
+
+  @Override
+  public void newLine() {
+    lines++;
+    if (!blankLine) {
+      nonBlankLines++;
+    }
+    blankLine = true;
+  }
+
+  @Override
+  public void handleIgnoreEoL(char c) {
+    if (!Character.isWhitespace(c)) {
+      blankLine = false;
+    }
+  }
+
+  @Override
+  public void eof() {
+    if (!blankLine) {
+      nonBlankLines++;
+    }
+  }
+
+  public int lines() {
+    return lines;
+  }
+
+  public int nonBlankLines() {
+    return nonBlankLines;
+  }
+
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineHashComputer.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineHashComputer.java
new file mode 100644 (file)
index 0000000..f371c71
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.batch.fs.internal.charhandler;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.sonar.api.batch.fs.internal.FileMetadata.LineHashConsumer;
+
+public class LineHashComputer extends CharHandler {
+  private final MessageDigest lineMd5Digest = DigestUtils.getMd5Digest();
+  private final CharsetEncoder encoder;
+  private final StringBuilder sb = new StringBuilder();
+  private final LineHashConsumer consumer;
+  private final File file;
+  private int line = 1;
+
+  public LineHashComputer(LineHashConsumer consumer, File f) {
+    this.consumer = consumer;
+    this.file = f;
+    this.encoder = StandardCharsets.UTF_8.newEncoder()
+      .onMalformedInput(CodingErrorAction.REPLACE)
+      .onUnmappableCharacter(CodingErrorAction.REPLACE);
+  }
+
+  @Override
+  public void handleIgnoreEoL(char c) {
+    if (!Character.isWhitespace(c)) {
+      sb.append(c);
+    }
+  }
+
+  @Override
+  public void newLine() {
+    processBuffer();
+    sb.setLength(0);
+    line++;
+  }
+
+  @Override
+  public void eof() {
+    if (this.line > 0) {
+      processBuffer();
+    }
+  }
+
+  private void processBuffer() {
+    try {
+      if (sb.length() > 0) {
+        ByteBuffer encoded = encoder.encode(CharBuffer.wrap(sb));
+        lineMd5Digest.update(encoded.array(), 0, encoded.limit());
+        consumer.consume(line, lineMd5Digest.digest());
+      }
+    } catch (CharacterCodingException e) {
+      throw new IllegalStateException("Error encoding line hash in file: " + file.getAbsolutePath(), e);
+    }
+  }
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineOffsetCounter.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineOffsetCounter.java
new file mode 100644 (file)
index 0000000..cf39d16
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.batch.fs.internal.charhandler;
+
+public class LineOffsetCounter extends CharHandler {
+  private long currentOriginalOffset = 0;
+  private IntArrayList originalLineOffsets = new IntArrayList();
+  private long lastValidOffset = 0;
+
+  public LineOffsetCounter() {
+    originalLineOffsets.add(0);
+  }
+
+  @Override
+  public void handleAll(char c) {
+    currentOriginalOffset++;
+  }
+
+  @Override
+  public void newLine() {
+    if (currentOriginalOffset > Integer.MAX_VALUE) {
+      throw new IllegalStateException("File is too big: " + currentOriginalOffset);
+    }
+    originalLineOffsets.add((int) currentOriginalOffset);
+  }
+
+  @Override
+  public void eof() {
+    lastValidOffset = currentOriginalOffset;
+  }
+
+  public int[] getOriginalLineOffsets() {
+    return originalLineOffsets.trimAndGet();
+  }
+
+  public int getLastValidOffset() {
+    if (lastValidOffset > Integer.MAX_VALUE) {
+      throw new IllegalStateException("File is too big: " + lastValidOffset);
+    }
+    return (int) lastValidOffset;
+  }
+
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/package-info.java
new file mode 100644 (file)
index 0000000..b8d3c63
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.api.batch.fs.internal.charhandler;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
index 7e8dffb59037ad1c8bc0cc4e7c240aeb996c61bf..c51c5ddf969e7d616683d7162e49483f6d936322 100644 (file)
@@ -23,12 +23,15 @@ import java.io.Serializable;
 import java.util.Collection;
 import java.util.List;
 import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.ThreadSafe;
+
 import org.sonar.api.batch.ScannerSide;
 
 /**
  * @since 4.5
  */
 @ScannerSide
+@ThreadSafe
 public interface MetricFinder {
 
   @CheckForNull
index bf96a7a1103875e62688b3a0bac1a130bae19fa0..c0df514efc083399c7f6a9815af9a1532e810149 100644 (file)
@@ -21,12 +21,15 @@ package org.sonar.api.batch.rule;
 
 import java.util.Map;
 import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.Immutable;
+
 import org.sonar.api.rule.RuleKey;
 
 /**
  * Configuration of a rule activated on a Quality profile
  * @since 4.2
  */
+@Immutable
 public interface ActiveRule {
 
   RuleKey ruleKey();
index 1ea2b013ed75c39def4d0733bdec91f262a878bb..f25c75a5af8f8226cd8cd54a774b7e12fc7f5eda 100644 (file)
@@ -23,6 +23,7 @@ import org.sonar.api.batch.ScannerSide;
 import org.sonar.api.rule.RuleKey;
 
 import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.Immutable;
 
 import java.util.Collection;
 
@@ -35,6 +36,7 @@ import java.util.Collection;
  *
  * @since 4.2
  */
+@Immutable
 @ScannerSide
 public interface ActiveRules {
 
index 7c06501dfbed96306b0afa99b2412a953ca9d672..4781bb39fdd76f5a41d71a7c257310d88aedc506 100644 (file)
  */
 package org.sonar.api.batch.rule;
 
+import javax.annotation.concurrent.Immutable;
+
 /**
  * @since 4.2
  */
+@Immutable
 public interface RuleParam {
   String key();
+
   String description();
 }
index 20b755c6fc7af7e4f52a88618a3e9150843e9dde..c73cc867eee544a3333ec1b014862e88470a0f7e 100644 (file)
@@ -23,6 +23,7 @@ import org.sonar.api.batch.ScannerSide;
 import org.sonar.api.rule.RuleKey;
 
 import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.Immutable;
 
 import java.util.Collection;
 
@@ -33,6 +34,7 @@ import java.util.Collection;
  * @since 4.2
  */
 @ScannerSide
+@Immutable
 public interface Rules {
 
   /**
index 9f4fd4bdbb034bed33cdfd4a3b86c6d33ee9bf9d..981ebbbedde57390f689dde3d5f39b0a78e3c326 100644 (file)
@@ -20,7 +20,6 @@
 package org.sonar.api.batch.rule.internal;
 
 import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.ListMultimap;
 import org.sonar.api.batch.rule.ActiveRule;
 import org.sonar.api.batch.rule.ActiveRules;
 import org.sonar.api.rule.RuleKey;
@@ -28,35 +27,32 @@ import org.sonar.api.rule.RuleKey;
 import javax.annotation.concurrent.Immutable;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
 @Immutable
 public class DefaultActiveRules implements ActiveRules {
-
-  // TODO use disk-backed cache (persistit) instead of full in-memory cache ?
-  private final ListMultimap<String, ActiveRule> activeRulesByRepository;
+  private final ImmutableListMultimap<String, ActiveRule> activeRulesByRepository;
   private final Map<String, Map<String, ActiveRule>> activeRulesByRepositoryAndKey = new HashMap<>();
   private final Map<String, Map<String, ActiveRule>> activeRulesByRepositoryAndInternalKey = new HashMap<>();
-  private final ListMultimap<String, ActiveRule> activeRulesByLanguage;
+  private final ImmutableListMultimap<String, ActiveRule> activeRulesByLanguage;
 
   public DefaultActiveRules(Collection<NewActiveRule> newActiveRules) {
     ImmutableListMultimap.Builder<String, ActiveRule> repoBuilder = ImmutableListMultimap.builder();
     ImmutableListMultimap.Builder<String, ActiveRule> langBuilder = ImmutableListMultimap.builder();
     for (NewActiveRule newAR : newActiveRules) {
       DefaultActiveRule ar = new DefaultActiveRule(newAR);
-      repoBuilder.put(ar.ruleKey().repository(), ar);
+      String repo = ar.ruleKey().repository();
+      repoBuilder.put(repo, ar);
       if (ar.language() != null) {
         langBuilder.put(ar.language(), ar);
       }
-      if (!activeRulesByRepositoryAndKey.containsKey(ar.ruleKey().repository())) {
-        activeRulesByRepositoryAndKey.put(ar.ruleKey().repository(), new HashMap<String, ActiveRule>());
-        activeRulesByRepositoryAndInternalKey.put(ar.ruleKey().repository(), new HashMap<String, ActiveRule>());
-      }
-      activeRulesByRepositoryAndKey.get(ar.ruleKey().repository()).put(ar.ruleKey().rule(), ar);
+
+      activeRulesByRepositoryAndKey.computeIfAbsent(repo, r -> new HashMap<>()).put(ar.ruleKey().rule(), ar);
       String internalKey = ar.internalKey();
       if (internalKey != null) {
-        activeRulesByRepositoryAndInternalKey.get(ar.ruleKey().repository()).put(internalKey, ar);
+        activeRulesByRepositoryAndInternalKey.computeIfAbsent(repo, r -> new HashMap<>()).put(internalKey, ar);
       }
     }
     activeRulesByRepository = repoBuilder.build();
@@ -65,11 +61,8 @@ public class DefaultActiveRules implements ActiveRules {
 
   @Override
   public ActiveRule find(RuleKey ruleKey) {
-    Map<String, ActiveRule> map = activeRulesByRepositoryAndKey.get(ruleKey.repository());
-    if(map != null) {
-      return map.get(ruleKey.rule());
-    }
-    return null;
+    return activeRulesByRepositoryAndKey.getOrDefault(ruleKey.repository(), Collections.emptyMap())
+      .get(ruleKey.rule());
   }
 
   @Override
index 37795039e83f8de293c58a2e6dba6e893cd155b6..1b46101fe24bc1502f5cf68d33fd9060c1a72bf6 100644 (file)
@@ -25,7 +25,6 @@ import com.google.common.collect.HashBasedTable;
 import org.sonar.api.batch.rule.Rule;
 import com.google.common.collect.Table;
 import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.ListMultimap;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.batch.rule.Rules;
 import org.sonar.api.rule.RuleKey;
@@ -39,9 +38,7 @@ import java.util.List;
 
 @Immutable
 class DefaultRules implements Rules {
-
-  // TODO use disk-backed cache (persistit) instead of full in-memory cache ?
-  private final ListMultimap<String, Rule> rulesByRepository;
+  private final ImmutableListMultimap<String, Rule> rulesByRepository;
   private final ImmutableTable<String, String, List<Rule>> rulesByRepositoryAndInternalKey;
 
   DefaultRules(Collection<NewRule> newRules) {
index abdb03c9ecc7005341bf4cd0e8761c405ae377f2..88e9c1d6b13b239c1a2f1b7668e601d0de19fefe 100644 (file)
@@ -57,7 +57,7 @@ public class DefaultCpdTokens extends DefaultStorable implements NewCpdTokens {
     this.inputFile = requireNonNull(inputFile, "file can't be null");
     String[] cpdExclusions = config.getStringArray(CoreProperties.CPD_EXCLUSIONS);
     for (PathPattern cpdExclusion : PathPattern.create(cpdExclusions)) {
-      if (cpdExclusion.match(inputFile)) {
+      if (cpdExclusion.match(inputFile.absolutePath(), inputFile.relativePath())) {
         this.excluded = true;
       }
     }
index 4cb9ded0ef5199dd8019d0f16d64eb61f0682817..65752a177c6bf42866c11e02aeba21135e42a86e 100644 (file)
@@ -28,6 +28,7 @@ import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.builder.ToStringBuilder;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.batch.fs.InputModule;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.component.Component;
 import org.sonar.api.scan.filesystem.PathResolver;
 
@@ -39,6 +40,10 @@ import org.sonar.api.scan.filesystem.PathResolver;
 public class Project extends Resource implements Component {
   private final ProjectDefinition definition;
 
+  public Project(DefaultInputModule module) {
+    this(module.definition());
+  }
+
   public Project(ProjectDefinition definition) {
     this.definition = definition;
     this.setKey(definition.getKey());
index fdc1dd19afd0f902e2bac78500fd3e8d792a30ac..17698835e8e4bcf6fa038e6f4e931422efcb40e9 100644 (file)
@@ -25,6 +25,8 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.Immutable;
+
 import org.apache.commons.io.FilenameUtils;
 import org.sonar.api.batch.ScannerSide;
 import org.sonar.api.utils.PathUtils;
@@ -35,6 +37,7 @@ import static java.util.stream.Collectors.joining;
  * @since 3.5
  */
 @ScannerSide
+@Immutable
 public class PathResolver {
 
   public File relativeFile(File dir, String path) {
index 6928b75e036854eeea56bff6da1bea4047b2f10a..76e1a74ec29101c69ac625544d47134ff6a1d057 100644 (file)
@@ -22,12 +22,14 @@ package org.sonar.api.scan.issue.filter;
 import java.util.Date;
 
 import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.ThreadSafe;
 
 import org.sonar.api.rule.RuleKey;
 
 /**
  * @since 5.3
  */
+@ThreadSafe
 public interface FilterableIssue {
 
   String componentKey();
index 230ad24605d5d5472d31042fd793982084c1daea..0c2847f7a1ea4cf98d1f09ecf74e3b32c7ff1332 100644 (file)
@@ -19,6 +19,9 @@
  */
 package org.sonar.api.scan.issue.filter;
 
+
+import javax.annotation.concurrent.ThreadSafe;
+
 import org.sonar.api.ExtensionPoint;
 import org.sonar.api.batch.ScannerSide;
 import org.sonarsource.api.sonarlint.SonarLintSide;
@@ -27,6 +30,7 @@ import org.sonarsource.api.sonarlint.SonarLintSide;
 @SonarLintSide
 @ExtensionPoint
 @FunctionalInterface
+@ThreadSafe
 /**
  * @since 5.3
  */
@@ -40,6 +44,9 @@ public interface IssueFilter {
    * </ul>
    * The <code>chain</code> parameter allows for fine control of the filtering logic: it is each filter's duty to either pass the issue to the next filter, by calling
    * the {@link IssueFilterChain#accept} method, or return directly if the issue has to be accepted or not
+   * 
+   * Implementations should be thread safe.
+   * 
    * @param issue the issue being filtered
    * @param chain the rest of the filters
    * @return <code>true</code> to accept the issue, <code>false</code> to reject it, {@link IssueFilterChain#accept} to let the other filters decide.
index aab918618ce8b1e9f5fa5dca757dbf3f00b21144..02b6943b63f4debca463c529cdc9dc63d1d023cf 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.api.scan.issue.filter;
 
+import javax.annotation.concurrent.ThreadSafe;
+
 /**
  * A filter chain is an object provided to issues filters for fine control over the filtering logic. Each filter has the choice to:
  * <ul>
@@ -29,6 +31,7 @@ package org.sonar.api.scan.issue.filter;
  * 
  * @since 5.3
  */
+@ThreadSafe
 public interface IssueFilterChain {
   /**
    * Called by a filter to let downstream filters decide the fate of the issue
index a1a0f0645690897e2e88a432de7fe8efc0840180..310e788d36d0bdf59c2f14f26fb3cd60362f1214 100644 (file)
  */
 package org.sonar.api.utils;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.regex.Pattern;
 import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
 import org.apache.commons.lang.StringUtils;
 
 /**
@@ -54,12 +57,12 @@ import org.apache.commons.lang.StringUtils;
  * <a href="https://github.com/JetBrains/intellij-community/blob/idea/107.743/platform/util/src/com/intellij/openapi/util/io/FileUtil.java#L847">FileUtil</a>
  * from IntelliJ OpenAPI.
  * 
- * 
  * @since 1.10
  */
+@ThreadSafe
 public class WildcardPattern {
 
-  private static final Map<String, WildcardPattern> CACHE = new HashMap<>();
+  private static final Map<String, WildcardPattern> CACHE = Collections.synchronizedMap(new HashMap<>());
   private static final String SPECIAL_CHARS = "()[]^$.{}+|";
 
   private Pattern pattern;
@@ -196,11 +199,6 @@ public class WildcardPattern {
    */
   public static WildcardPattern create(String pattern, String directorySeparator) {
     String key = pattern + directorySeparator;
-    WildcardPattern wildcardPattern = CACHE.get(key);
-    if (wildcardPattern == null) {
-      wildcardPattern = new WildcardPattern(pattern, directorySeparator);
-      CACHE.put(key, wildcardPattern);
-    }
-    return wildcardPattern;
+    return CACHE.computeIfAbsent(key, k -> new WildcardPattern(pattern, directorySeparator));
   }
 }
index 0ae07c5e8d98a53f7f3362bb225489cc3453d829..82ccf11b3e80ab486f5209a8565e088b3966be6d 100644 (file)
@@ -54,7 +54,7 @@ public class DefaultInputFileTest {
     Path baseDir = temp.newFolder().toPath();
 
     Metadata metadata = new Metadata(42, 42, "", new int[0], 0);
-    DefaultIndexedFile indexedFile = new DefaultIndexedFile("ABCDE", baseDir, "src/Foo.php", InputFile.Type.TEST, 0).setLanguage("php");
+    DefaultIndexedFile indexedFile = new DefaultIndexedFile("ABCDE", baseDir, "src/Foo.php", InputFile.Type.TEST, "php", 0);
     DefaultInputFile inputFile = new DefaultInputFile(indexedFile, (f) -> f.setMetadata(metadata))
       .setStatus(InputFile.Status.ADDED)
       .setCharset(StandardCharsets.ISO_8859_1);
@@ -82,10 +82,9 @@ public class DefaultInputFileTest {
 
     Metadata metadata = new Metadata(42, 30, "", new int[0], 0);
 
-    DefaultInputFile inputFile = new DefaultInputFile(new DefaultIndexedFile("ABCDE", baseDir, "src/Foo.php", InputFile.Type.TEST, 0)
-      .setLanguage("php"), f -> f.setMetadata(metadata))
-        .setStatus(InputFile.Status.ADDED)
-        .setCharset(StandardCharsets.ISO_8859_1);
+    DefaultInputFile inputFile = new DefaultInputFile(new DefaultIndexedFile("ABCDE", baseDir, "src/Foo.php", InputFile.Type.TEST, "php", 0), f -> f.setMetadata(metadata))
+      .setStatus(InputFile.Status.ADDED)
+      .setCharset(StandardCharsets.ISO_8859_1);
 
     assertThat(inputFile.contents()).isEqualTo(content);
     try (InputStream inputStream = inputFile.inputStream()) {
@@ -110,10 +109,9 @@ public class DefaultInputFileTest {
 
     Metadata metadata = new Metadata(42, 30, "", new int[0], 0);
 
-    DefaultInputFile inputFile = new DefaultInputFile(new DefaultIndexedFile("ABCDE", baseDir, "src/Foo.php", InputFile.Type.TEST, 0)
-      .setLanguage("php"), f -> f.setMetadata(metadata))
-        .setStatus(InputFile.Status.ADDED)
-        .setCharset(StandardCharsets.UTF_8);
+    DefaultInputFile inputFile = new DefaultInputFile(new DefaultIndexedFile("ABCDE", baseDir, "src/Foo.php", InputFile.Type.TEST, "php", 0), f -> f.setMetadata(metadata))
+      .setStatus(InputFile.Status.ADDED)
+      .setCharset(StandardCharsets.UTF_8);
 
     assertThat(inputFile.contents()).isEqualTo(content);
     try (InputStream inputStream = inputFile.inputStream()) {
@@ -125,9 +123,9 @@ public class DefaultInputFileTest {
 
   @Test
   public void test_equals_and_hashcode() throws Exception {
-    DefaultInputFile f1 = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php"), (f) -> mock(Metadata.class));
-    DefaultInputFile f1a = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php"), (f) -> mock(Metadata.class));
-    DefaultInputFile f2 = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Bar.php"), (f) -> mock(Metadata.class));
+    DefaultInputFile f1 = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php", null), (f) -> mock(Metadata.class));
+    DefaultInputFile f1a = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php", null), (f) -> mock(Metadata.class));
+    DefaultInputFile f2 = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Bar.php", null), (f) -> mock(Metadata.class));
 
     assertThat(f1).isEqualTo(f1);
     assertThat(f1).isEqualTo(f1a);
@@ -141,14 +139,14 @@ public class DefaultInputFileTest {
 
   @Test
   public void test_toString() throws Exception {
-    DefaultInputFile file = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php"), (f) -> mock(Metadata.class));
+    DefaultInputFile file = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php", null), (f) -> mock(Metadata.class));
     assertThat(file.toString()).isEqualTo("[moduleKey=ABCDE, relative=src/Foo.php, basedir=module]");
   }
 
   @Test
   public void checkValidPointer() {
     Metadata metadata = new Metadata(2, 2, "", new int[] {0, 10}, 15);
-    DefaultInputFile file = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php"), f -> f.setMetadata(metadata));
+    DefaultInputFile file = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php", null), f -> f.setMetadata(metadata));
     assertThat(file.newPointer(1, 0).line()).isEqualTo(1);
     assertThat(file.newPointer(1, 0).lineOffset()).isEqualTo(0);
     // Don't fail
@@ -185,7 +183,7 @@ public class DefaultInputFileTest {
   @Test
   public void checkValidPointerUsingGlobalOffset() {
     Metadata metadata = new Metadata(2, 2, "", new int[] {0, 10}, 15);
-    DefaultInputFile file = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php"), f -> f.setMetadata(metadata));
+    DefaultInputFile file = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php", null), f -> f.setMetadata(metadata));
     assertThat(file.newPointer(0).line()).isEqualTo(1);
     assertThat(file.newPointer(0).lineOffset()).isEqualTo(0);
 
@@ -216,7 +214,7 @@ public class DefaultInputFileTest {
   @Test
   public void checkValidRange() {
     Metadata metadata = new FileMetadata().readMetadata(new StringReader("bla bla a\nabcde"));
-    DefaultInputFile file = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php"), f -> f.setMetadata(metadata));
+    DefaultInputFile file = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php", null), f -> f.setMetadata(metadata));
 
     assertThat(file.newRange(file.newPointer(1, 0), file.newPointer(2, 1)).start().line()).isEqualTo(1);
     // Don't fail
@@ -242,7 +240,7 @@ public class DefaultInputFileTest {
   @Test
   public void selectLine() {
     Metadata metadata = new FileMetadata().readMetadata(new StringReader("bla bla a\nabcde\n\nabc"));
-    DefaultInputFile file = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php"), f -> f.setMetadata(metadata));
+    DefaultInputFile file = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php", null), f -> f.setMetadata(metadata));
 
     assertThat(file.selectLine(1).start().line()).isEqualTo(1);
     assertThat(file.selectLine(1).start().lineOffset()).isEqualTo(0);
@@ -266,7 +264,7 @@ public class DefaultInputFileTest {
   @Test
   public void checkValidRangeUsingGlobalOffset() {
     Metadata metadata = new Metadata(2, 2, "", new int[] {0, 10}, 15);
-    DefaultInputFile file = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php"), f -> f.setMetadata(metadata));
+    DefaultInputFile file = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php", null), f -> f.setMetadata(metadata));
     TextRange newRange = file.newRange(10, 13);
     assertThat(newRange.start().line()).isEqualTo(2);
     assertThat(newRange.start().lineOffset()).isEqualTo(0);
@@ -277,7 +275,7 @@ public class DefaultInputFileTest {
   @Test
   public void testRangeOverlap() {
     Metadata metadata = new Metadata(2, 2, "", new int[] {0, 10}, 15);
-    DefaultInputFile file = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php"), f -> f.setMetadata(metadata));
+    DefaultInputFile file = new DefaultInputFile(new DefaultIndexedFile("ABCDE", Paths.get("module"), "src/Foo.php", null), f -> f.setMetadata(metadata));
     // Don't fail
     assertThat(file.newRange(file.newPointer(1, 0), file.newPointer(1, 1)).overlap(file.newRange(file.newPointer(1, 0), file.newPointer(1, 1)))).isTrue();
     assertThat(file.newRange(file.newPointer(1, 0), file.newPointer(1, 1)).overlap(file.newRange(file.newPointer(1, 0), file.newPointer(1, 2)))).isTrue();
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/fs/internal/IntArrayListTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/fs/internal/IntArrayListTest.java
deleted file mode 100644 (file)
index c9c7e19..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.api.batch.fs.internal;
-
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class IntArrayListTest {
-
-  @Test
-  public void addElements() {
-    IntArrayList list = new IntArrayList();
-    assertThat(list.trimAndGet()).isEmpty();
-    list.add(1);
-    list.add(2);
-    assertThat(list.trimAndGet()).containsExactly(1, 2);
-  }
-
-  @Test
-  public void trimIfNeeded() {
-    IntArrayList list = new IntArrayList();
-    list.add(1);
-    list.add(2);
-    assertThat(list.trimAndGet()).isSameAs(list.trimAndGet());
-  }
-
-  @Test
-  public void grow() {
-    // Default capacity is 10
-    IntArrayList list = new IntArrayList();
-    for (int i = 1; i <= 11; i++) {
-      list.add(i);
-    }
-    assertThat(list.trimAndGet()).hasSize(11);
-  }
-
-}
index 0337f6044b13a280b3efa67b5c9a875e8a9fe979..fac72032858bc246c58ac7660c129fa5f4da29ae 100644 (file)
@@ -45,26 +45,26 @@ public class PathPatternTest {
     PathPattern pattern = PathPattern.create("**/*Foo.java");
     assertThat(pattern.toString()).isEqualTo("**/*Foo.java");
 
-    IndexedFile indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/MyFoo.java");
-    assertThat(pattern.match(indexedFile)).isTrue();
+    IndexedFile indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/MyFoo.java", null);
+    assertThat(pattern.match(indexedFile.absolutePath(), indexedFile.relativePath())).isTrue();
 
     // case sensitive by default
-    indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/MyFoo.JAVA");
-    assertThat(pattern.match(indexedFile)).isFalse();
+    indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/MyFoo.JAVA", null);
+    assertThat(pattern.match(indexedFile.absolutePath(), indexedFile.relativePath())).isFalse();
 
-    indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/Other.java");
-    assertThat(pattern.match(indexedFile)).isFalse();
+    indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/Other.java", null);
+    assertThat(pattern.match(indexedFile.absolutePath(), indexedFile.relativePath())).isFalse();
   }
 
   @Test
   public void match_relative_path_and_insensitive_file_extension() throws Exception {
     PathPattern pattern = PathPattern.create("**/*Foo.java");
 
-    IndexedFile indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/MyFoo.JAVA");
-    assertThat(pattern.match(indexedFile, false)).isTrue();
+    IndexedFile indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/MyFoo.JAVA", null);
+    assertThat(pattern.match(indexedFile.absolutePath(), indexedFile.relativePath(), false)).isTrue();
 
-    indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/Other.java");
-    assertThat(pattern.match(indexedFile, false)).isFalse();
+    indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/Other.java", null);
+    assertThat(pattern.match(indexedFile.absolutePath(), indexedFile.relativePath(), false)).isFalse();
   }
 
   @Test
@@ -72,15 +72,15 @@ public class PathPatternTest {
     PathPattern pattern = PathPattern.create("file:**/src/main/**Foo.java");
     assertThat(pattern.toString()).isEqualTo("file:**/src/main/**Foo.java");
 
-    IndexedFile indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/MyFoo.java");
-    assertThat(pattern.match(indexedFile)).isTrue();
+    IndexedFile indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/MyFoo.java", null);
+    assertThat(pattern.match(indexedFile.absolutePath(), indexedFile.relativePath())).isTrue();
 
     // case sensitive by default
-    indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/MyFoo.JAVA");
-    assertThat(pattern.match(indexedFile)).isFalse();
+    indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/MyFoo.JAVA", null);
+    assertThat(pattern.match(indexedFile.absolutePath(), indexedFile.relativePath())).isFalse();
 
-    indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/Other.java");
-    assertThat(pattern.match(indexedFile)).isFalse();
+    indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/Other.java", null);
+    assertThat(pattern.match(indexedFile.absolutePath(), indexedFile.relativePath())).isFalse();
   }
 
   @Test
@@ -88,11 +88,11 @@ public class PathPatternTest {
     PathPattern pattern = PathPattern.create("file:**/src/main/**Foo.java");
     assertThat(pattern.toString()).isEqualTo("file:**/src/main/**Foo.java");
 
-    IndexedFile indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/MyFoo.JAVA");
-    assertThat(pattern.match(indexedFile, false)).isTrue();
+    IndexedFile indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/MyFoo.JAVA", null);
+    assertThat(pattern.match(indexedFile.absolutePath(), indexedFile.relativePath(), false)).isTrue();
 
-    indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/Other.JAVA");
-    assertThat(pattern.match(indexedFile, false)).isFalse();
+    indexedFile = new DefaultIndexedFile("ABCDE", moduleBasePath, "src/main/java/org/Other.JAVA", null);
+    assertThat(pattern.match(indexedFile.absolutePath(), indexedFile.relativePath(), false)).isFalse();
   }
 
   @Test
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/fs/internal/charhandler/IntArrayListTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/fs/internal/charhandler/IntArrayListTest.java
new file mode 100644 (file)
index 0000000..a4e931e
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.batch.fs.internal.charhandler;
+
+import org.junit.Test;
+import org.sonar.api.batch.fs.internal.charhandler.IntArrayList;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class IntArrayListTest {
+
+  @Test
+  public void addElements() {
+    IntArrayList list = new IntArrayList();
+    assertThat(list.trimAndGet()).isEmpty();
+    list.add(1);
+    list.add(2);
+    assertThat(list.trimAndGet()).containsExactly(1, 2);
+  }
+
+  @Test
+  public void trimIfNeeded() {
+    IntArrayList list = new IntArrayList();
+    list.add(1);
+    list.add(2);
+    assertThat(list.trimAndGet()).isSameAs(list.trimAndGet());
+  }
+
+  @Test
+  public void grow() {
+    // Default capacity is 10
+    IntArrayList list = new IntArrayList();
+    for (int i = 1; i <= 11; i++) {
+      list.add(i);
+    }
+    assertThat(list.trimAndGet()).hasSize(11);
+  }
+
+}
index 0c0b42bef0a52ab7d9fb9eb60e7366fbd133da2c..28d1af3517b33441b10375549f0410cd27d61edf 100644 (file)
  */
 package org.sonar.scanner;
 
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
+import static java.util.stream.Collectors.toMap;
+
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
+
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.batch.measure.MetricFinder;
 import org.sonar.api.batch.sensor.SensorContext;
@@ -35,10 +35,11 @@ import org.sonar.api.utils.KeyValueFormat;
 import org.sonar.api.utils.KeyValueFormat.Converter;
 import org.sonar.scanner.scan.measure.MeasureCache;
 
-import static java.util.stream.Collectors.toMap;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
 
 public class DefaultFileLinesContext implements FileLinesContext {
-
   private final SensorContext context;
   private final InputFile inputFile;
   private final MetricFinder metricFinder;
@@ -47,7 +48,7 @@ public class DefaultFileLinesContext implements FileLinesContext {
   /**
    * metric key -> line -> value
    */
-  private final Map<String, Map<Integer, Object>> map = Maps.newHashMap();
+  private final Map<String, Map<Integer, Object>> map = new HashMap<>();
 
   public DefaultFileLinesContext(SensorContext context, InputFile inputFile, MetricFinder metricFinder, MeasureCache measureCache) {
     this.context = context;
@@ -73,13 +74,7 @@ public class DefaultFileLinesContext implements FileLinesContext {
   public Integer getIntValue(String metricKey, int line) {
     Preconditions.checkNotNull(metricKey);
     checkLineRange(line);
-
-    Map<Integer, Object> lines = map.get(metricKey);
-    if (lines == null) {
-      // not in memory, so load
-      lines = loadData(metricKey, KeyValueFormat.newIntegerConverter());
-      map.put(metricKey, lines);
-    }
+    Map<Integer, Object> lines = map.computeIfAbsent(metricKey, k -> loadData(k, KeyValueFormat.newIntegerConverter()));
     return (Integer) lines.get(line);
   }
 
@@ -96,27 +91,13 @@ public class DefaultFileLinesContext implements FileLinesContext {
   public String getStringValue(String metricKey, int line) {
     Preconditions.checkNotNull(metricKey);
     checkLineRange(line);
-
-    Map<Integer, Object> lines = map.get(metricKey);
-    if (lines == null) {
-      // not in memory, so load
-      lines = loadData(metricKey, KeyValueFormat.newStringConverter());
-      map.put(metricKey, lines);
-    }
+    Map<Integer, Object> lines = map.computeIfAbsent(metricKey, k -> loadData(k, KeyValueFormat.newStringConverter()));
     return (String) lines.get(line);
   }
 
-  private Map<Integer, Object> getOrCreateLines(String metricKey) {
-    Map<Integer, Object> lines = map.get(metricKey);
-    if (lines == null) {
-      lines = Maps.newHashMap();
-      map.put(metricKey, lines);
-    }
-    return lines;
-  }
-
   private void setValue(String metricKey, int line, Object value) {
-    getOrCreateLines(metricKey).put(line, value);
+    map.computeIfAbsent(metricKey, k -> new HashMap<>())
+      .put(line, value);
   }
 
   @Override
index 40d7cbcd3c7613109bfb3e5fcc11e150a0aaf665..c0eed526886b6da6a5223adfdfe2d23ac2589b46 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.scanner;
 
 import java.util.Date;
 import java.util.Optional;
+
 import org.picocontainer.Startable;
 import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.ScannerSide;
@@ -31,6 +32,8 @@ import org.sonar.api.utils.System2;
 
 /**
  * @since 6.3
+ * 
+ * Immutable after {@link #start()}
  */
 @ScannerSide
 public class ProjectAnalysisInfo implements Startable {
index dc4a860533c155c4deaaa8e5ae18ec8ce5182fa4..1c0ec317ad74759af9bf48de1d4aeb1119246cc1 100644 (file)
@@ -22,10 +22,11 @@ package org.sonar.scanner.analysis;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+
 import org.picocontainer.ComponentLifecycle;
 import org.picocontainer.PicoContainer;
 import org.picocontainer.injectors.ProviderAdapter;
-import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.api.utils.TempFolder;
 import org.sonar.api.utils.internal.DefaultTempFolder;
 
@@ -34,9 +35,9 @@ public class AnalysisTempFolderProvider extends ProviderAdapter implements Compo
   private DefaultTempFolder projectTempFolder;
   private boolean started = false;
 
-  public TempFolder provide(ProjectReactor projectReactor) {
+  public TempFolder provide(InputModuleHierarchy moduleHierarchy) {
     if (projectTempFolder == null) {
-      Path workingDir = projectReactor.getRoot().getWorkDir().toPath();
+      Path workingDir = moduleHierarchy.root().getWorkDir().toPath();
       Path tempDir = workingDir.normalize().resolve(TMP_NAME);
       try {
         Files.deleteIfExists(tempDir);
@@ -64,7 +65,7 @@ public class AnalysisTempFolderProvider extends ProviderAdapter implements Compo
 
   @Override
   public void dispose(PicoContainer container) {
-    //nothing to do
+    // nothing to do
   }
 
   @Override
index 9414be0017f11c14a5c9217e80441c3f2871e31b..28fefb1080ada697975532943d081149d84b4f94 100644 (file)
@@ -21,6 +21,8 @@ package org.sonar.scanner.analysis;
 
 import java.util.Map;
 import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.Immutable;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.CoreProperties;
@@ -30,6 +32,7 @@ import org.sonar.scanner.bootstrap.GlobalProperties;
 /**
  * @since 4.0
  */
+@Immutable
 public class DefaultAnalysisMode extends AbstractAnalysisMode {
 
   private static final Logger LOG = LoggerFactory.getLogger(DefaultAnalysisMode.class);
index 76b37d87b299ee896ce8d8ed47ff256a9bcfdd10..743294be0f8e2ac91048cb0918cbfdb84d71fb68 100644 (file)
  */
 package org.sonar.scanner.bootstrap;
 
+import javax.annotation.concurrent.Immutable;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.CoreProperties;
 
+@Immutable
 public class GlobalMode extends AbstractAnalysisMode {
   private static final Logger LOG = LoggerFactory.getLogger(GlobalMode.class);
 
index 70749f70fc0f7850afccb17e09d04ba77597b2e7..0117f735ba6ffd3aa6a8c1748085eda6e4b253f9 100644 (file)
@@ -30,6 +30,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.stream.Collectors;
 import javax.annotation.Nullable;
+
 import org.apache.commons.lang.ClassUtils;
 import org.sonar.api.batch.CheckProject;
 import org.sonar.api.batch.DependedUpon;
@@ -279,7 +280,7 @@ public class ScannerExtensionDictionnary {
       || (org.sonar.api.batch.Sensor.class.equals(type) && ClassUtils.isAssignable(extension.getClass(), Sensor.class)))
       && (matcher == null || matcher.accept(extension));
     if (keep && module != null && ClassUtils.isAssignable(extension.getClass(), CheckProject.class)) {
-      keep = ((CheckProject) extension).shouldExecuteOnProject(new Project(module.definition()));
+      keep = ((CheckProject) extension).shouldExecuteOnProject(new Project(module));
     }
     return keep;
   }
index 87a0794c1e5d49050c067c75b61f1d2e151ffefb..e8e1d804b3688c21aabf7ff9a66b483909ba7f69 100644 (file)
@@ -19,9 +19,8 @@
  */
 package org.sonar.scanner.cpd;
 
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
+import static com.google.common.collect.FluentIterable.from;
+
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
@@ -30,10 +29,10 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+
 import org.sonar.api.batch.fs.InputComponent;
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.batch.fs.internal.DefaultInputComponent;
-import org.sonar.api.config.Configuration;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.duplications.block.Block;
@@ -49,7 +48,9 @@ import org.sonar.scanner.report.ReportPublisher;
 import org.sonar.scanner.scan.filesystem.InputComponentStore;
 import org.sonar.scanner.util.ProgressReport;
 
-import static com.google.common.collect.FluentIterable.from;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
 
 /**
  * Runs on the root module, at the end of the project analysis.
@@ -66,12 +67,12 @@ public class CpdExecutor {
   private final SonarCpdBlockIndex index;
   private final ReportPublisher publisher;
   private final InputComponentStore componentStore;
-  private final Configuration settings;
   private final ProgressReport progressReport;
+  private final CpdSettings settings;
   private int count;
   private int total;
 
-  public CpdExecutor(Configuration settings, SonarCpdBlockIndex index, ReportPublisher publisher, InputComponentStore inputComponentCache) {
+  public CpdExecutor(CpdSettings settings, SonarCpdBlockIndex index, ReportPublisher publisher, InputComponentStore inputComponentCache) {
     this.settings = settings;
     this.index = index;
     this.publisher = publisher;
@@ -139,7 +140,8 @@ public class CpdExecutor {
 
     List<CloneGroup> filtered;
     if (!"java".equalsIgnoreCase(inputFile.language())) {
-      Predicate<CloneGroup> minimumTokensPredicate = DuplicationPredicates.numberOfUnitsNotLessThan(getMinimumTokens(inputFile.language()));
+      int minTokens = settings.getMinimumTokens(inputFile.language());
+      Predicate<CloneGroup> minimumTokensPredicate = DuplicationPredicates.numberOfUnitsNotLessThan(minTokens);
       filtered = from(duplications).filter(minimumTokensPredicate).toList();
     } else {
       filtered = duplications;
@@ -148,17 +150,6 @@ public class CpdExecutor {
     saveDuplications(component, filtered);
   }
 
-  @VisibleForTesting
-  /**
-   * Not applicable to Java, as the {@link BlockChunker} that it uses does not record start and end units of each block. 
-   * Also, it uses statements instead of tokens. 
-   * @param languageKey
-   * @return
-   */
-  int getMinimumTokens(String languageKey) {
-    return settings.getInt("sonar.cpd." + languageKey + ".minimumTokens").orElse(100);
-  }
-
   @VisibleForTesting
   final void saveDuplications(final DefaultInputComponent component, List<CloneGroup> duplications) {
     if (duplications.size() > MAX_CLONE_GROUP_PER_FILE) {
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/cpd/CpdSettings.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/cpd/CpdSettings.java
new file mode 100644 (file)
index 0000000..7335d85
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.cpd;
+
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
+import org.sonar.api.config.Configuration;
+import org.sonar.duplications.block.BlockChunker;
+
+public class CpdSettings {
+  private final Configuration settings;
+  private final String branch;
+
+  public CpdSettings(Configuration settings, InputModuleHierarchy hierarchy) {
+    this.settings = settings;
+    this.branch = hierarchy.root().getBranch();
+  }
+
+  public boolean isCrossProjectDuplicationEnabled() {
+    return settings.getBoolean(CoreProperties.CPD_CROSS_PROJECT).orElse(false)
+      // No cross project duplication for branches
+      && StringUtils.isBlank(branch);
+  }
+
+  /**
+   * Not applicable to Java, as the {@link BlockChunker} that it uses does not record start and end units of each block. 
+   * Also, it uses statements instead of tokens. 
+   * @param languageKey
+   * @return
+   */
+  int getMinimumTokens(String languageKey) {
+    return settings.getInt("sonar.cpd." + languageKey + ".minimumTokens").orElse(100);
+  }
+}
index 2a4cdf9eab0215ba105497c9a85c62a1ba76aa58..a5bc77ba3e6ef5c847bb8cc227bcdf67482bc477 100644 (file)
@@ -24,10 +24,9 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
 import java.util.stream.Collectors;
-import org.sonar.api.CoreProperties;
+
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.batch.fs.internal.DefaultInputFile;
-import org.sonar.api.config.Configuration;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.duplications.block.Block;
@@ -36,6 +35,7 @@ import org.sonar.duplications.index.AbstractCloneIndex;
 import org.sonar.duplications.index.CloneIndex;
 import org.sonar.duplications.index.PackedMemoryCloneIndex;
 import org.sonar.duplications.index.PackedMemoryCloneIndex.ResourceBlocks;
+import org.sonar.scanner.cpd.CpdSettings;
 import org.sonar.scanner.protocol.output.FileStructure;
 import org.sonar.scanner.protocol.output.ScannerReport;
 import org.sonar.scanner.report.ReportPublisher;
@@ -44,17 +44,17 @@ public class SonarCpdBlockIndex extends AbstractCloneIndex {
   private static final Logger LOG = Loggers.get(SonarCpdBlockIndex.class);
   private final CloneIndex mem = new PackedMemoryCloneIndex();
   private final ReportPublisher publisher;
-  private final Configuration settings;
   // Files already tokenized
   private final Set<InputFile> indexedFiles = new HashSet<>();
+  private final CpdSettings settings;
 
-  public SonarCpdBlockIndex(ReportPublisher publisher, Configuration settings) {
+  public SonarCpdBlockIndex(ReportPublisher publisher, CpdSettings settings) {
     this.publisher = publisher;
     this.settings = settings;
   }
 
   public void insert(InputFile inputFile, Collection<Block> blocks) {
-    if (isCrossProjectDuplicationEnabled(settings)) {
+    if (settings.isCrossProjectDuplicationEnabled()) {
       int id = ((DefaultInputFile) inputFile).batchId();
       if (publisher.getWriter().hasComponentData(FileStructure.Domain.CPD_TEXT_BLOCKS, id)) {
         throw new UnsupportedOperationException("Trying to save CPD tokens twice for the same file is not supported: " + inputFile.absolutePath());
@@ -87,12 +87,6 @@ public class SonarCpdBlockIndex extends AbstractCloneIndex {
     return indexedFiles.contains(inputFile);
   }
 
-  public static boolean isCrossProjectDuplicationEnabled(Configuration settings) {
-    return settings.getBoolean(CoreProperties.CPD_CROSS_PROJECT).orElse(false)
-      // No cross project duplication for branches
-      && !settings.get(CoreProperties.PROJECT_BRANCH_PROPERTY).isPresent();
-  }
-
   public Collection<Block> getByInputFile(String resourceKey) {
     return mem.getByResourceId(resourceKey);
   }
index 1690a8a44be44d09ec9876c6d51c4a29b6adb0ac..14eb7316b40573a33b30a5e35dfd8beba7e3fdae 100644 (file)
@@ -123,7 +123,7 @@ public class DefaultIndex {
     if (component == null) {
       throw new IllegalStateException("Invalid component key: " + key);
     }
-    if (sensorStorage.isDeprecatedMetric(measure.getMetricKey())) {
+    if (DefaultSensorStorage.isDeprecatedMetric(measure.getMetricKey())) {
       // Ignore deprecated metrics
       return measure;
     }
@@ -187,7 +187,7 @@ public class DefaultIndex {
     } else if (inputComponent instanceof InputFile) {
       r = File.create(((InputFile) inputComponent).relativePath());
     } else if (inputComponent instanceof InputModule) {
-      r = new Project(((DefaultInputModule) inputComponent).definition());
+      r = new Project(((DefaultInputModule) inputComponent));
     } else {
       throw new IllegalArgumentException("Unknow input path type: " + inputComponent);
     }
index ccc86732f23f9c17fdb846af1cd18e32d84a5f65..a0331d07aa9e26a3a4174ccaf9b8f7683d42c1bd 100644 (file)
@@ -20,6 +20,9 @@
 package org.sonar.scanner.issue;
 
 import java.util.Date;
+
+import javax.annotation.concurrent.ThreadSafe;
+
 import org.apache.commons.lang.builder.ToStringBuilder;
 import org.apache.commons.lang.builder.ToStringStyle;
 import org.sonar.api.batch.fs.InputModule;
@@ -29,6 +32,7 @@ import org.sonar.api.scan.issue.filter.FilterableIssue;
 import org.sonar.scanner.ProjectAnalysisInfo;
 import org.sonar.scanner.protocol.output.ScannerReport.Issue;
 
+@ThreadSafe
 public class DefaultFilterableIssue implements FilterableIssue {
   private final Issue rawIssue;
   private final ProjectAnalysisInfo projectAnalysisInfo;
index d0f01cc432dab88d417c541782fac25c30161a65..fb8105bfd58087962f55705b8ec05a35a00a4e09 100644 (file)
@@ -22,10 +22,14 @@ package org.sonar.scanner.issue;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+
+import javax.annotation.concurrent.ThreadSafe;
+
 import org.sonar.api.scan.issue.filter.FilterableIssue;
 import org.sonar.api.scan.issue.filter.IssueFilter;
 import org.sonar.api.scan.issue.filter.IssueFilterChain;
 
+@ThreadSafe
 public class DefaultIssueFilterChain implements IssueFilterChain {
   private final List<IssueFilter> filters;
 
index db9105a3f90df5bf1970c59d2dadf20e4178e39c..f31865db0cf1c5ce62156412d1043fe7286cb30e 100644 (file)
@@ -30,14 +30,14 @@ import org.sonar.scanner.protocol.output.ScannerReport;
 
 @ScannerSide
 public class IssueFilters {
-  private final IssueFilter[] filters;
+  private final IssueFilterChain filterChain;
   private final org.sonar.api.issue.batch.IssueFilter[] deprecatedFilters;
   private final DefaultInputModule module;
   private final ProjectAnalysisInfo projectAnalysisInfo;
 
   public IssueFilters(DefaultInputModule module, ProjectAnalysisInfo projectAnalysisInfo, IssueFilter[] exclusionFilters, org.sonar.api.issue.batch.IssueFilter[] filters) {
     this.module = module;
-    this.filters = exclusionFilters;
+    this.filterChain = new DefaultIssueFilterChain(exclusionFilters);
     this.deprecatedFilters = filters;
     this.projectAnalysisInfo = projectAnalysisInfo;
   }
@@ -55,7 +55,6 @@ public class IssueFilters {
   }
 
   public boolean accept(String componentKey, ScannerReport.Issue rawIssue) {
-    IssueFilterChain filterChain = new DefaultIssueFilterChain(filters);
     FilterableIssue fIssue = new DefaultFilterableIssue(module, projectAnalysisInfo, rawIssue, componentKey);
     if (filterChain.accept(fIssue)) {
       return acceptDeprecated(componentKey, rawIssue);
index 874e4514cf99aa0336237ed0d53d710511431d3a..279b003d93300cbdba6a5302cdcd805e8e5f9d89 100644 (file)
@@ -19,7 +19,8 @@
  */
 package org.sonar.scanner.issue;
 
-import com.google.common.base.Strings;
+import javax.annotation.concurrent.ThreadSafe;
+
 import org.sonar.api.batch.fs.TextRange;
 import org.sonar.api.batch.fs.internal.DefaultInputComponent;
 import org.sonar.api.batch.rule.ActiveRule;
@@ -35,9 +36,12 @@ import org.sonar.scanner.protocol.output.ScannerReport;
 import org.sonar.scanner.protocol.output.ScannerReport.IssueLocation;
 import org.sonar.scanner.report.ReportPublisher;
 
+import com.google.common.base.Strings;
+
 /**
  * Initialize the issues raised during scan.
  */
+@ThreadSafe
 public class ModuleIssues {
 
   private final ActiveRules activeRules;
@@ -62,9 +66,19 @@ public class ModuleIssues {
       return false;
     }
 
-    String primaryMessage = Strings.isNullOrEmpty(issue.primaryLocation().message()) ? rule.name() : issue.primaryLocation().message();
+    ScannerReport.Issue rawIssue = createReportIssue(issue, inputComponent.batchId(), rule.name(), activeRule.severity());
+
+    if (filters.accept(inputComponent.key(), rawIssue)) {
+      write(inputComponent.batchId(), rawIssue);
+      return true;
+    }
+    return false;
+  }
+
+  private static ScannerReport.Issue createReportIssue(Issue issue, int batchId, String ruleName, String activeRuleSeverity) {
+    String primaryMessage = Strings.isNullOrEmpty(issue.primaryLocation().message()) ? ruleName : issue.primaryLocation().message();
     org.sonar.api.batch.rule.Severity overriddenSeverity = issue.overriddenSeverity();
-    Severity severity = overriddenSeverity != null ? Severity.valueOf(overriddenSeverity.name()) : Severity.valueOf(activeRule.severity());
+    Severity severity = overriddenSeverity != null ? Severity.valueOf(overriddenSeverity.name()) : Severity.valueOf(activeRuleSeverity);
 
     ScannerReport.Issue.Builder builder = ScannerReport.Issue.newBuilder();
     ScannerReport.IssueLocation.Builder locationBuilder = IssueLocation.newBuilder();
@@ -76,7 +90,7 @@ public class ModuleIssues {
     builder.setMsg(primaryMessage);
     locationBuilder.setMsg(primaryMessage);
 
-    locationBuilder.setComponentRef(inputComponent.batchId());
+    locationBuilder.setComponentRef(batchId);
     TextRange primaryTextRange = issue.primaryLocation().textRange();
     if (primaryTextRange != null) {
       builder.setTextRange(toProtobufTextRange(textRangeBuilder, primaryTextRange));
@@ -86,13 +100,7 @@ public class ModuleIssues {
       builder.setGap(gap);
     }
     applyFlows(builder, locationBuilder, textRangeBuilder, issue);
-    ScannerReport.Issue rawIssue = builder.build();
-
-    if (filters.accept(inputComponent.key(), rawIssue)) {
-      write(inputComponent.batchId(), rawIssue);
-      return true;
-    }
-    return false;
+    return builder.build();
   }
 
   private static void applyFlows(ScannerReport.Issue.Builder builder, ScannerReport.IssueLocation.Builder locationBuilder,
index 66efeb194f33ac296d45250f1d3b1c5d61cc7e19..dc96c80d2d15e1953d969d9375f3043538ee9189 100644 (file)
  */
 package org.sonar.scanner.issue.ignore;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
 import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.ThreadSafe;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.batch.fs.InputComponent;
@@ -31,14 +37,15 @@ import org.sonar.scanner.issue.ignore.pattern.IssueInclusionPatternInitializer;
 import org.sonar.scanner.issue.ignore.pattern.IssuePattern;
 import org.sonar.scanner.scan.filesystem.InputComponentStore;
 
+@ThreadSafe
 public class EnforceIssuesFilter implements IssueFilter {
   private static final Logger LOG = LoggerFactory.getLogger(EnforceIssuesFilter.class);
 
-  private IssueInclusionPatternInitializer patternInitializer;
-  private InputComponentStore componentStore;
+  private final List<IssuePattern> multicriteriaPatterns;
+  private final InputComponentStore componentStore;
 
   public EnforceIssuesFilter(IssueInclusionPatternInitializer patternInitializer, InputComponentStore componentStore) {
-    this.patternInitializer = patternInitializer;
+    this.multicriteriaPatterns = Collections.unmodifiableList(new ArrayList<>(patternInitializer.getMulticriteriaPatterns()));
     this.componentStore = componentStore;
   }
 
@@ -48,7 +55,7 @@ public class EnforceIssuesFilter implements IssueFilter {
     boolean atLeastOnePatternFullyMatched = false;
     IssuePattern matchingPattern = null;
 
-    for (IssuePattern pattern : patternInitializer.getMulticriteriaPatterns()) {
+    for (IssuePattern pattern : multicriteriaPatterns) {
       if (pattern.getRulePattern().match(issue.ruleKey().toString())) {
         atLeastOneRuleMatched = true;
         String relativePath = getRelativePath(issue.componentKey());
index 4d326f7c1984a009863f2cd3873fec85a861faff..689d4158d8d0db946a2218c6a42dbcfd8ce066f7 100644 (file)
@@ -24,17 +24,20 @@ import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.Set;
 import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
 import org.apache.commons.lang.builder.ToStringBuilder;
 import org.apache.commons.lang.builder.ToStringStyle;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.utils.WildcardPattern;
 
+@Immutable
 public class IssuePattern {
 
   private final WildcardPattern resourcePattern;
   private final WildcardPattern rulePattern;
-  private final Set<Integer> lines = new LinkedHashSet<>();
-  private final Set<LineRange> lineRanges = new LinkedHashSet<>();
+  private final Set<Integer> lines;
+  private final Set<LineRange> lineRanges;
   private final boolean checkLines;
 
   public IssuePattern(String resourcePattern, String rulePattern) {
@@ -45,13 +48,19 @@ public class IssuePattern {
     this.resourcePattern = WildcardPattern.create(resourcePattern);
     this.rulePattern = WildcardPattern.create(rulePattern);
     this.checkLines = !lineRanges.isEmpty();
+    Set<Integer> modifiableLines = new LinkedHashSet<>();
+    Set<LineRange> modifiableLineRanges = new LinkedHashSet<>();
+
     for (LineRange range : lineRanges) {
       if (range.from() == range.to()) {
-        this.lines.add(range.from());
+        modifiableLines.add(range.from());
       } else {
-        this.lineRanges.add(range);
+        modifiableLineRanges.add(range);
       }
     }
+
+    this.lines = Collections.unmodifiableSet(modifiableLines);
+    this.lineRanges = Collections.unmodifiableSet(modifiableLineRanges);
   }
 
   public WildcardPattern getResourcePattern() {
index 4bdf149ec05772a12bcc4923cb7161aba111dda8..df5ce6c6f1d89a6c3fbebb0e5e00fa02fd7cd70e 100644 (file)
@@ -23,7 +23,7 @@ import java.util.ArrayList;
 import java.util.List;
 import javax.annotation.CheckForNull;
 import org.apache.commons.lang.StringUtils;
-import org.sonar.api.batch.fs.internal.FileMetadata.CharHandler;
+import org.sonar.api.batch.fs.internal.charhandler.CharHandler;
 import org.sonar.scanner.issue.ignore.pattern.BlockIssuePattern;
 import org.sonar.scanner.issue.ignore.pattern.IssueExclusionPatternInitializer;
 import org.sonar.scanner.issue.ignore.pattern.IssuePattern;
index e0230681382c0b40ef94aadba5eaa5d03678e85f..3611b6d7ea53b32d8e17a91ffc50daf80e684588 100644 (file)
@@ -27,7 +27,7 @@ import java.util.regex.Pattern;
 import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.sonar.api.batch.fs.internal.FileMetadata.CharHandler;
+import org.sonar.api.batch.fs.internal.charhandler.CharHandler;
 import org.sonar.scanner.issue.ignore.pattern.LineRange;
 import org.sonar.scanner.issue.ignore.pattern.PatternMatcher;
 import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader.DoubleRegexpMatcher;
@@ -57,19 +57,19 @@ public class IssueExclusionsRegexpScanner extends CharHandler {
   }
 
   @Override
-  protected void handleIgnoreEoL(char c) {
+  public void handleIgnoreEoL(char c) {
     sb.append(c);
   }
 
   @Override
-  protected void newLine() {
+  public void newLine() {
     processLine(sb.toString());
     sb.setLength(0);
     lineIndex++;
   }
 
   @Override
-  protected void eof() {
+  public void eof() {
     processLine(sb.toString());
 
     if (currentMatcher != null && !currentMatcher.hasSecondPattern()) {
index 1d659f99c9c55afecc4b02aa36fd78a5f8b3886e..8671b672c7405fbc6c86477731e6f218bfc23858 100644 (file)
@@ -21,16 +21,16 @@ package org.sonar.scanner.issue.tracking;
 
 import org.sonar.api.batch.InstantiationStrategy;
 import org.sonar.api.batch.ScannerSide;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.batch.fs.InputComponent;
+import org.sonar.api.batch.fs.InputModule;
 import org.sonar.api.batch.fs.internal.DefaultInputComponent;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.api.utils.log.Profiler;
 import org.sonar.core.component.ComponentKeys;
 import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue;
 import org.sonar.scanner.repository.ServerIssuesLoader;
-import org.sonar.scanner.scan.ImmutableProjectReactor;
 import org.sonar.scanner.scan.filesystem.InputComponentStore;
 import org.sonar.scanner.storage.Storage;
 import org.sonar.scanner.storage.Storages;
@@ -45,21 +45,20 @@ public class ServerIssueRepository {
   private final Storages caches;
   private Storage<ServerIssue> issuesCache;
   private final ServerIssuesLoader previousIssuesLoader;
-  private final ImmutableProjectReactor reactor;
-  private final InputComponentStore resourceCache;
+  private final InputComponentStore componentStore;
 
-  public ServerIssueRepository(Storages caches, ServerIssuesLoader previousIssuesLoader, ImmutableProjectReactor reactor, InputComponentStore resourceCache) {
+  public ServerIssueRepository(Storages caches, ServerIssuesLoader previousIssuesLoader, InputComponentStore componentStore) {
     this.caches = caches;
     this.previousIssuesLoader = previousIssuesLoader;
-    this.reactor = reactor;
-    this.resourceCache = resourceCache;
+    this.componentStore = componentStore;
   }
 
   public void load() {
     Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG);
     this.issuesCache = caches.createCache("previousIssues");
     caches.registerValueCoder(ServerIssue.class, new ServerIssueValueCoder());
-    previousIssuesLoader.load(reactor.getRoot().getKeyWithBranch(), this::store);
+    DefaultInputModule root = (DefaultInputModule) componentStore.root();
+    previousIssuesLoader.load(root.getKeyWithBranch(), this::store);
     profiler.stopInfo();
   }
 
@@ -69,10 +68,10 @@ public class ServerIssueRepository {
 
   private void store(ServerIssue issue) {
     String moduleKeyWithBranch = issue.getModuleKey();
-    ProjectDefinition projectDefinition = reactor.getProjectDefinition(moduleKeyWithBranch);
-    if (projectDefinition != null) {
-      String componentKeyWithoutBranch = ComponentKeys.createEffectiveKey(projectDefinition.getKey(), issue.hasPath() ? issue.getPath() : null);
-      DefaultInputComponent r = (DefaultInputComponent) resourceCache.getByKey(componentKeyWithoutBranch);
+    InputModule module = componentStore.getModule(moduleKeyWithBranch);
+    if (module != null) {
+      String componentKeyWithoutBranch = ComponentKeys.createEffectiveKey(module.key(), issue.hasPath() ? issue.getPath() : null);
+      DefaultInputComponent r = (DefaultInputComponent) componentStore.getByKey(componentKeyWithoutBranch);
       if (r != null) {
         issuesCache.put(r.batchId(), issue.getKey(), issue);
         return;
index 46acfff0279b1c1f1b81f6fe7f4e43293b5920c4..e4e5c81356e12995dc35e9b8d339eab22b7349da 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.scanner.phases;
 import org.sonar.api.batch.SensorContext;
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.batch.fs.internal.DefaultInputModule;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.scanner.events.BatchStepEvent;
 import org.sonar.scanner.events.EventBus;
 import org.sonar.scanner.issue.ignore.scanner.IssueExclusionsLoader;
@@ -40,9 +41,10 @@ public abstract class AbstractPhaseExecutor {
   private final DefaultModuleFileSystem fs;
   private final QProfileVerifier profileVerifier;
   private final IssueExclusionsLoader issueExclusionsLoader;
+  private final InputModuleHierarchy hierarchy;
 
   public AbstractPhaseExecutor(InitializersExecutor initializersExecutor, PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor,
-    SensorContext sensorContext, EventBus eventBus, FileSystemLogger fsLogger, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier,
+    SensorContext sensorContext, InputModuleHierarchy hierarchy, EventBus eventBus, FileSystemLogger fsLogger, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier,
     IssueExclusionsLoader issueExclusionsLoader) {
     this.postJobsExecutor = postJobsExecutor;
     this.initializersExecutor = initializersExecutor;
@@ -53,6 +55,7 @@ public abstract class AbstractPhaseExecutor {
     this.fs = fs;
     this.profileVerifier = profileVerifier;
     this.issueExclusionsLoader = issueExclusionsLoader;
+    this.hierarchy = hierarchy;
   }
 
   /**
@@ -76,7 +79,7 @@ public abstract class AbstractPhaseExecutor {
 
     afterSensors();
 
-    if (module.definition().getParent() == null) {
+    if (hierarchy.isRoot(module)) {
       executeOnRoot();
       postJobsExecutor.execute(sensorContext);
     }
index b4d5818fc4773b3601ff8f10b8213229a0a4c35f..556e6298652c21d4a643e724e39c2bc7ff906f74 100644 (file)
@@ -19,8 +19,8 @@
  */
 package org.sonar.scanner.phases;
 
-import com.google.common.collect.Lists;
 import java.util.Collection;
+
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.batch.Initializer;
 import org.sonar.api.batch.fs.internal.DefaultInputModule;
@@ -31,6 +31,8 @@ import org.sonar.api.utils.log.Profiler;
 import org.sonar.scanner.bootstrap.ScannerExtensionDictionnary;
 import org.sonar.scanner.events.EventBus;
 
+import com.google.common.collect.Lists;
+
 public class InitializersExecutor {
 
   private static final Logger LOG = Loggers.get(SensorsExecutor.class);
@@ -52,7 +54,7 @@ public class InitializersExecutor {
       LOG.debug("Initializers : {}", StringUtils.join(initializers, " -> "));
     }
 
-    Project project = new Project(module.definition());
+    Project project = new Project(module);
     for (Initializer initializer : initializers) {
       eventBus.fireEvent(new InitializerExecutionEvent(initializer, true));
 
index 7893e6a0024e9be72367418c9951be2830253ce3..e2fd0aea9ff178928272c226120f78050d9eb6d5 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.scanner.phases;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.batch.SensorContext;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.scanner.events.BatchStepEvent;
 import org.sonar.scanner.events.EventBus;
 import org.sonar.scanner.issue.IssueCallback;
@@ -43,8 +44,8 @@ public final class IssuesPhaseExecutor extends AbstractPhaseExecutor {
 
   public IssuesPhaseExecutor(InitializersExecutor initializersExecutor, PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, SensorContext sensorContext,
     EventBus eventBus, FileSystemLogger fsLogger, IssuesReports jsonReport, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier,
-    IssueExclusionsLoader issueExclusionsLoader, IssueTransition localIssueTracking, IssueCallback issueCallback) {
-    super(initializersExecutor, postJobsExecutor, sensorsExecutor, sensorContext, eventBus, fsLogger, fs, profileVerifier, issueExclusionsLoader);
+    IssueExclusionsLoader issueExclusionsLoader, IssueTransition localIssueTracking, IssueCallback issueCallback, InputModuleHierarchy moduleHierarchy) {
+    super(initializersExecutor, postJobsExecutor, sensorsExecutor, sensorContext, moduleHierarchy, eventBus, fsLogger, fs, profileVerifier, issueExclusionsLoader);
     this.eventBus = eventBus;
     this.issuesReport = jsonReport;
     this.localIssueTracking = localIssueTracking;
index b3b33277f6690736b7805dfa9b949152e5574a78..d4b42a5f97c3a5e2cec4ca4fe12a001b6b52278c 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.scanner.phases;
 
 import java.util.ArrayList;
 import java.util.Collection;
+
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.batch.PostJob;
 import org.sonar.api.batch.ScannerSide;
@@ -58,7 +59,7 @@ public class PostJobsExecutor {
   private void execute(SensorContext context, Collection<PostJob> postJobs) {
     logPostJobs(postJobs);
 
-    Project project = new Project(module.definition());
+    Project project = new Project(module);
     for (PostJob postJob : postJobs) {
       LOG.info("Executing post-job {}", ScannerUtils.describe(postJob));
       eventBus.fireEvent(new PostJobExecutionEvent(postJob, true));
index d8a5d9fcac84c87396b10e7ef6a3f8fe30d2d5b1..63868c8e2cb20c2bf8e685125f199595b877f49a 100644 (file)
@@ -34,7 +34,7 @@ class ProjectAnalysisEvent extends AbstractPhaseEvent<ProjectAnalysisHandler>
 
   @Override
   public Project getProject() {
-    return new Project(module.definition());
+    return new Project(module);
   }
 
   @Override
index 434c88a96ddb7a48ca328479d8d3dcaa1114a7b2..84ff4b4d4bc3e2267f9ea606ca31b4323529e21f 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.scanner.phases;
 
 import org.sonar.api.batch.SensorContext;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.scanner.cpd.CpdExecutor;
 import org.sonar.scanner.events.BatchStepEvent;
 import org.sonar.scanner.events.EventBus;
@@ -38,9 +39,9 @@ public final class PublishPhaseExecutor extends AbstractPhaseExecutor {
   private final ScmPublisher scm;
 
   public PublishPhaseExecutor(InitializersExecutor initializersExecutor, PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, SensorContext sensorContext,
-    EventBus eventBus, ReportPublisher reportPublisher, FileSystemLogger fsLogger, DefaultModuleFileSystem fs,
-    QProfileVerifier profileVerifier, IssueExclusionsLoader issueExclusionsLoader, CpdExecutor cpdExecutor, ScmPublisher scm) {
-    super(initializersExecutor, postJobsExecutor, sensorsExecutor, sensorContext, eventBus, fsLogger, fs, profileVerifier, issueExclusionsLoader);
+    EventBus eventBus, ReportPublisher reportPublisher, FileSystemLogger fsLogger, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier, 
+    IssueExclusionsLoader issueExclusionsLoader, CpdExecutor cpdExecutor, ScmPublisher scm, InputModuleHierarchy hierarchy) {
+    super(initializersExecutor, postJobsExecutor, sensorsExecutor, sensorContext, hierarchy, eventBus, fsLogger, fs, profileVerifier, issueExclusionsLoader);
     this.eventBus = eventBus;
     this.reportPublisher = reportPublisher;
     this.cpdExecutor = cpdExecutor;
index c4b1805d3baad299218a81c722b382837e870d1b..88158bfb0655964ef98dbba79295622bd9fe6783 100644 (file)
@@ -27,6 +27,7 @@ import org.sonar.api.batch.ScannerSide;
 import org.sonar.api.batch.Sensor;
 import org.sonar.api.batch.SensorContext;
 import org.sonar.api.batch.fs.internal.DefaultInputModule;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.api.resources.Project;
 import org.sonar.scanner.bootstrap.ScannerExtensionDictionnary;
 import org.sonar.scanner.events.EventBus;
@@ -40,12 +41,12 @@ public class SensorsExecutor {
   private final SensorStrategy strategy;
   private final boolean isRoot;
 
-  public SensorsExecutor(ScannerExtensionDictionnary selector, DefaultInputModule module, EventBus eventBus, SensorStrategy strategy) {
+  public SensorsExecutor(ScannerExtensionDictionnary selector, DefaultInputModule module, InputModuleHierarchy hierarchy, EventBus eventBus, SensorStrategy strategy) {
     this.selector = selector;
     this.module = module;
     this.eventBus = eventBus;
     this.strategy = strategy;
-    this.isRoot = module.definition().getParent() == null;
+    this.isRoot = hierarchy.isRoot(module);
   }
 
   public void execute(SensorContext context) {
@@ -84,7 +85,7 @@ public class SensorsExecutor {
 
   private void executeSensor(SensorContext context, Sensor sensor) {
     eventBus.fireEvent(new SensorExecutionEvent(sensor, true));
-    sensor.analyse(new Project(module.definition()), context);
+    sensor.analyse(new Project(module), context);
     eventBus.fireEvent(new SensorExecutionEvent(sensor, false));
   }
 }
index 4da1aeda04e1351dce45079a7092d237b7952af5..0ff003378b4ad8eb61f62ca6885bce63dc280d62 100644 (file)
@@ -29,10 +29,12 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 import java.util.TreeSet;
+
 import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.AnalysisMode;
 import org.sonar.api.batch.ScannerSide;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
@@ -56,16 +58,18 @@ public class AnalysisContextReportPublisher {
   private final System2 system;
   private final ProjectRepositories projectRepos;
   private final GlobalConfiguration globalSettings;
+  private final InputModuleHierarchy hierarchy;
 
   private ScannerReportWriter writer;
 
   public AnalysisContextReportPublisher(AnalysisMode mode, ScannerPluginRepository pluginRepo, System2 system,
-    ProjectRepositories projectRepos, GlobalConfiguration globalSettings) {
+    ProjectRepositories projectRepos, GlobalConfiguration globalSettings, InputModuleHierarchy hierarchy) {
     this.mode = mode;
     this.pluginRepo = pluginRepo;
     this.system = system;
     this.projectRepos = projectRepos;
     this.globalSettings = globalSettings;
+    this.hierarchy = hierarchy;
   }
 
   public void init(ScannerReportWriter writer) {
@@ -120,15 +124,15 @@ public class AnalysisContextReportPublisher {
     }
   }
 
-  public void dumpModuleSettings(ProjectDefinition moduleDefinition) {
+  public void dumpModuleSettings(DefaultInputModule module) {
     if (mode.isIssues()) {
       return;
     }
 
     File analysisLog = writer.getFileStructure().analysisLog();
     try (BufferedWriter fileWriter = Files.newBufferedWriter(analysisLog.toPath(), StandardCharsets.UTF_8, StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
-      Map<String, String> moduleSpecificProps = collectModuleSpecificProps(moduleDefinition);
-      fileWriter.append(String.format("Settings for module: %s", moduleDefinition.getKey())).append('\n');
+      Map<String, String> moduleSpecificProps = collectModuleSpecificProps(module);
+      fileWriter.append(String.format("Settings for module: %s", module.key())).append('\n');
       for (String prop : new TreeSet<>(moduleSpecificProps.keySet())) {
         if (isSystemProp(prop) || isEnvVariable(prop) || !isSqProp(prop)) {
           continue;
@@ -147,17 +151,17 @@ public class AnalysisContextReportPublisher {
   /**
    * Only keep props that are not in parent
    */
-  private Map<String, String> collectModuleSpecificProps(ProjectDefinition moduleDefinition) {
+  private Map<String, String> collectModuleSpecificProps(DefaultInputModule module) {
     Map<String, String> moduleSpecificProps = new HashMap<>();
-    if (projectRepos.moduleExists(moduleDefinition.getKeyWithBranch())) {
-      moduleSpecificProps.putAll(projectRepos.settings(moduleDefinition.getKeyWithBranch()));
+    if (projectRepos.moduleExists(module.getKeyWithBranch())) {
+      moduleSpecificProps.putAll(projectRepos.settings(module.getKeyWithBranch()));
     }
-    ProjectDefinition parent = moduleDefinition.getParent();
+    DefaultInputModule parent = hierarchy.parent(module);
     if (parent == null) {
-      moduleSpecificProps.putAll(moduleDefinition.properties());
+      moduleSpecificProps.putAll(module.properties());
     } else {
       Map<String, String> parentProps = parent.properties();
-      for (Map.Entry<String, String> entry : moduleDefinition.properties().entrySet()) {
+      for (Map.Entry<String, String> entry : module.properties().entrySet()) {
         if (!parentProps.containsKey(entry.getKey()) || !parentProps.get(entry.getKey()).equals(entry.getValue())) {
           moduleSpecificProps.put(entry.getKey(), entry.getValue());
         }
index 78fa6bea16c0b09e1ffca767d11ab29c192a5ffe..395b6e4b63b015c6444bf8edc98e69acd6e8924d 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.scanner.report;
 import java.util.Collection;
 import java.util.stream.Collectors;
 import javax.annotation.CheckForNull;
+
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
index 9afa52f2ca82ec7dc62701d35981e10917d400a9..871f65ca0f069dd7254d5e5e370273c3d5f3b7f6 100644 (file)
@@ -25,7 +25,7 @@ import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.api.config.Configuration;
 import org.sonar.scanner.ProjectAnalysisInfo;
-import org.sonar.scanner.cpd.index.SonarCpdBlockIndex;
+import org.sonar.scanner.cpd.CpdSettings;
 import org.sonar.scanner.protocol.output.ScannerReport;
 import org.sonar.scanner.protocol.output.ScannerReportWriter;
 import org.sonar.scanner.rule.ModuleQProfiles;
@@ -37,12 +37,15 @@ public class MetadataPublisher implements ReportPublisherStep {
   private final ModuleQProfiles qProfiles;
   private final ProjectAnalysisInfo projectAnalysisInfo;
   private final InputModuleHierarchy moduleHierarchy;
+  private final CpdSettings cpdSettings;
 
-  public MetadataPublisher(ProjectAnalysisInfo projectAnalysisInfo, InputModuleHierarchy moduleHierarchy, Configuration settings, ModuleQProfiles qProfiles) {
+  public MetadataPublisher(ProjectAnalysisInfo projectAnalysisInfo, InputModuleHierarchy moduleHierarchy, Configuration settings,
+    ModuleQProfiles qProfiles, CpdSettings cpdSettings) {
     this.projectAnalysisInfo = projectAnalysisInfo;
     this.moduleHierarchy = moduleHierarchy;
     this.settings = settings;
     this.qProfiles = qProfiles;
+    this.cpdSettings = cpdSettings;
   }
 
   @Override
@@ -53,7 +56,7 @@ public class MetadataPublisher implements ReportPublisherStep {
       .setAnalysisDate(projectAnalysisInfo.analysisDate().getTime())
       // Here we want key without branch
       .setProjectKey(rootDef.getKey())
-      .setCrossProjectDuplicationActivated(SonarCpdBlockIndex.isCrossProjectDuplicationEnabled(settings))
+      .setCrossProjectDuplicationActivated(cpdSettings.isCrossProjectDuplicationEnabled())
       .setRootComponentRef(rootProject.batchId());
 
     settings.get(CoreProperties.PROJECT_ORGANIZATION_PROPERTY).ifPresent(builder::setOrganizationKey);
index 487c48cd4e5fce6f46926f7289e64ec686cf8e0e..54fd94ca9236d72a55b32c6486c994251dfa1674 100644 (file)
@@ -19,8 +19,8 @@
  */
 package org.sonar.scanner.report;
 
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Throwables;
+import static org.sonar.core.util.FileUtils.deleteQuietly;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -31,13 +31,14 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.LinkedHashMap;
 import java.util.Map;
+
 import javax.annotation.Nullable;
-import okhttp3.HttpUrl;
+
 import org.apache.commons.io.FileUtils;
 import org.picocontainer.Startable;
 import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.ScannerSide;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.platform.Server;
 import org.sonar.api.utils.MessageException;
@@ -48,14 +49,16 @@ import org.sonar.api.utils.log.Loggers;
 import org.sonar.scanner.analysis.DefaultAnalysisMode;
 import org.sonar.scanner.bootstrap.ScannerWsClient;
 import org.sonar.scanner.protocol.output.ScannerReportWriter;
-import org.sonar.scanner.scan.ImmutableProjectReactor;
 import org.sonarqube.ws.MediaTypes;
 import org.sonarqube.ws.WsCe;
 import org.sonarqube.ws.client.HttpException;
 import org.sonarqube.ws.client.PostRequest;
 import org.sonarqube.ws.client.WsResponse;
 
-import static org.sonar.core.util.FileUtils.deleteQuietly;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Throwables;
+
+import okhttp3.HttpUrl;
 
 @ScannerSide
 public class ReportPublisher implements Startable {
@@ -69,7 +72,7 @@ public class ReportPublisher implements Startable {
   private final Configuration settings;
   private final ScannerWsClient wsClient;
   private final AnalysisContextReportPublisher contextPublisher;
-  private final ImmutableProjectReactor projectReactor;
+  private final InputModuleHierarchy moduleHierarchy;
   private final DefaultAnalysisMode analysisMode;
   private final TempFolder temp;
   private final ReportPublisherStep[] publishers;
@@ -79,12 +82,12 @@ public class ReportPublisher implements Startable {
   private ScannerReportWriter writer;
 
   public ReportPublisher(Configuration settings, ScannerWsClient wsClient, Server server, AnalysisContextReportPublisher contextPublisher,
-    ImmutableProjectReactor projectReactor, DefaultAnalysisMode analysisMode, TempFolder temp, ReportPublisherStep[] publishers) {
+    InputModuleHierarchy moduleHierarchy, DefaultAnalysisMode analysisMode, TempFolder temp, ReportPublisherStep[] publishers) {
     this.settings = settings;
     this.wsClient = wsClient;
     this.server = server;
     this.contextPublisher = contextPublisher;
-    this.projectReactor = projectReactor;
+    this.moduleHierarchy = moduleHierarchy;
     this.analysisMode = analysisMode;
     this.temp = temp;
     this.publishers = publishers;
@@ -92,7 +95,7 @@ public class ReportPublisher implements Startable {
 
   @Override
   public void start() {
-    reportDir = new File(projectReactor.getRoot().getWorkDir(), "batch-report");
+    reportDir = new File(moduleHierarchy.root().getWorkDir(), "batch-report");
     writer = new ScannerReportWriter(reportDir);
     contextPublisher.init(writer);
 
@@ -165,14 +168,13 @@ public class ReportPublisher implements Startable {
   String upload(File report) {
     LOG.debug("Upload report");
     long startTime = System.currentTimeMillis();
-    ProjectDefinition projectDefinition = projectReactor.getRoot();
     PostRequest.Part filePart = new PostRequest.Part(MediaTypes.ZIP, report);
     PostRequest post = new PostRequest("api/ce/submit")
       .setMediaType(MediaTypes.PROTOBUF)
       .setParam("organization", settings.get(CoreProperties.PROJECT_ORGANIZATION_PROPERTY).orElse(null))
-      .setParam("projectKey", projectDefinition.getKey())
-      .setParam("projectName", projectDefinition.getOriginalName())
-      .setParam("projectBranch", projectDefinition.getBranch())
+      .setParam("projectKey", moduleHierarchy.root().key())
+      .setParam("projectName", moduleHierarchy.root().getOriginalName())
+      .setParam("projectBranch", moduleHierarchy.root().getBranch())
       .setPart("report", filePart);
 
     WsResponse response;
@@ -201,7 +203,7 @@ public class ReportPublisher implements Startable {
       HttpUrl httpUrl = HttpUrl.parse(publicUrl);
 
       Map<String, String> metadata = new LinkedHashMap<>();
-      String effectiveKey = projectReactor.getRoot().getKeyWithBranch();
+      String effectiveKey = moduleHierarchy.root().getKeyWithBranch();
       settings.get(CoreProperties.PROJECT_ORGANIZATION_PROPERTY).ifPresent(org -> metadata.put("organization", org));
       metadata.put("projectKey", effectiveKey);
       metadata.put("serverUrl", publicUrl);
@@ -230,7 +232,7 @@ public class ReportPublisher implements Startable {
   }
 
   private void dumpMetadata(Map<String, String> metadata) {
-    Path file = projectReactor.getRoot().getWorkDir().toPath().resolve(METADATA_DUMP_FILENAME);
+    Path file = moduleHierarchy.root().getWorkDir().toPath().resolve(METADATA_DUMP_FILENAME);
     try (Writer output = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) {
       for (Map.Entry<String, String> entry : metadata.entrySet()) {
         output.write(entry.getKey());
index c6c0015a88ec8189db286599081a0656aabc988f..4c36c0991ad8e4ea2f93678e13c664035439d567 100644 (file)
  */
 package org.sonar.scanner.repository;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 import java.util.HashMap;
 import java.util.Map;
-import org.sonar.api.batch.ScannerSide;
 
-import static com.google.common.base.Preconditions.checkArgument;
+import org.sonar.api.batch.ScannerSide;
 
 @ScannerSide
 public class ContextPropertiesCache {
index 6c9921c53a20355073ab6df80f58a3bf9e5680e3..ead8f33b9091b2e2652770aed72300d0e1372b44 100644 (file)
  */
 package org.sonar.scanner.repository;
 
-import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ImmutableTable;
 import com.google.common.collect.Table;
 import java.util.Date;
 import java.util.Map;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
 
+@Immutable
 public class ProjectRepositories {
-  private final Table<String, String, String> settingsByModule;
-  private final Table<String, String, FileData> fileDataByModuleAndPath;
+  private final ImmutableTable<String, String, String> settingsByModule;
+  private final ImmutableTable<String, String, FileData> fileDataByModuleAndPath;
   private final Date lastAnalysisDate;
   private final boolean exists;
 
   public ProjectRepositories() {
     this.exists = false;
-    this.settingsByModule = HashBasedTable.create();
-    this.fileDataByModuleAndPath = HashBasedTable.create();
+    this.settingsByModule = new ImmutableTable.Builder<String, String, String>().build();
+    this.fileDataByModuleAndPath = new ImmutableTable.Builder<String, String, FileData>().build();
     this.lastAnalysisDate = null;
   }
 
   public ProjectRepositories(Table<String, String, String> settingsByModule, Table<String, String, FileData> fileDataByModuleAndPath,
     @Nullable Date lastAnalysisDate) {
-    this.settingsByModule = settingsByModule;
-    this.fileDataByModuleAndPath = fileDataByModuleAndPath;
+    this.settingsByModule = ImmutableTable.copyOf(settingsByModule);
+    this.fileDataByModuleAndPath = ImmutableTable.copyOf(fileDataByModuleAndPath);
     this.lastAnalysisDate = lastAnalysisDate;
     this.exists = true;
   }
index 4af9e6a6e740ef7b1c627868403bc9d2e47aa911..8866237e54cd1c7f0d269f53ccaf16b68dec84c8 100644 (file)
@@ -21,7 +21,10 @@ package org.sonar.scanner.repository.language;
 
 import java.util.ArrayList;
 import java.util.Collection;
+
 import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.Immutable;
+
 import org.picocontainer.Startable;
 import org.sonar.api.resources.Languages;
 
@@ -29,6 +32,7 @@ import org.sonar.api.resources.Languages;
  * Languages repository using {@link Languages}
  * @since 4.4
  */
+@Immutable
 public class DefaultLanguagesRepository implements LanguagesRepository, Startable {
 
   private Languages languages;
index 8b90c65f57e5333911c5ee0a0c207a64175316bc..775c895bfdb15e37481173a2144faccd09bda341 100644 (file)
@@ -22,6 +22,9 @@ package org.sonar.scanner.repository.language;
 import java.util.Arrays;
 import java.util.Collection;
 
+import javax.annotation.concurrent.Immutable;
+
+@Immutable
 public final class Language {
 
   private final String key;
index 626a3361f8fd8ae1ad333e0c870900f47ab5be1e..d6f5046ea66dcf21aa4b578e104d6ccc691caa21 100644 (file)
 package org.sonar.scanner.repository.language;
 
 import java.util.Collection;
+
 import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.Immutable;
+
 import org.sonar.api.batch.ScannerSide;
 
 /**
@@ -28,6 +31,7 @@ import org.sonar.api.batch.ScannerSide;
  * @since 4.4
  */
 @ScannerSide
+@Immutable
 public interface LanguagesRepository {
 
   /**
index 013e22e352e94a741bb12ec4388d487b1f579d0f..30d87568514ad6764c2c6abd51ef956caeb56082 100644 (file)
  */
 package org.sonar.scanner.rule;
 
+import org.sonar.api.utils.DateUtils;
+
+import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile;
+import org.sonar.api.batch.ScannerSide;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.Immutable;
+
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
-import javax.annotation.CheckForNull;
-import org.sonar.api.batch.ScannerSide;
-import org.sonar.api.utils.DateUtils;
-import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile;
 
 /**
  * Lists the Quality profiles enabled on the current module.
  */
 @ScannerSide
+@Immutable
 public class ModuleQProfiles {
 
   public static final String SONAR_PROFILE_PROP = "sonar.profile";
@@ -42,11 +47,11 @@ public class ModuleQProfiles {
 
     for (QualityProfile qProfile : profiles) {
       map.put(qProfile.getLanguage(),
-        new QProfile()
+        new QProfile.Builder()
           .setKey(qProfile.getKey())
           .setName(qProfile.getName())
           .setLanguage(qProfile.getLanguage())
-          .setRulesUpdatedAt(DateUtils.parseDateTime(qProfile.getRulesUpdatedAt())));
+          .setRulesUpdatedAt(DateUtils.parseDateTime(qProfile.getRulesUpdatedAt())).build());
     }
     byLanguage = Collections.unmodifiableMap(map);
   }
index a38f942b9291baeb3884a583b820d8104ba3ded1..9568368b992eaf1a6010366d83efda3e2fd40ad8 100644 (file)
@@ -22,49 +22,38 @@ package org.sonar.scanner.rule;
 import com.google.common.base.MoreObjects;
 import java.util.Date;
 
+import javax.annotation.concurrent.Immutable;
+
+@Immutable
 public class QProfile {
+  private final String key;
+  private final String name;
+  private final String language;
+  private final Date rulesUpdatedAt;
 
-  private String key;
-  private String name;
-  private String language;
-  private Date rulesUpdatedAt;
+  public QProfile(String key, String name, String language, Date rulesUpdatedAt) {
+    this.key = key;
+    this.name = name;
+    this.language = language;
+    this.rulesUpdatedAt = rulesUpdatedAt;
+  }
 
   public String getKey() {
     return key;
   }
 
-  public QProfile setKey(String key) {
-    this.key = key;
-    return this;
-  }
-
   public String getName() {
     return name;
   }
 
-  public QProfile setName(String name) {
-    this.name = name;
-    return this;
-  }
-
   public String getLanguage() {
     return language;
   }
 
-  public QProfile setLanguage(String language) {
-    this.language = language;
-    return this;
-  }
-
   public Date getRulesUpdatedAt() {
     return rulesUpdatedAt;
   }
 
-  public QProfile setRulesUpdatedAt(Date d) {
-    this.rulesUpdatedAt = d;
-    return this;
-  }
-
   @Override
   public boolean equals(Object o) {
     if (this == o) {
@@ -92,4 +81,51 @@ public class QProfile {
       .add("rulesUpdatedAt", rulesUpdatedAt)
       .toString();
   }
+
+  public static class Builder {
+    private String key;
+    private String name;
+    private String language;
+    private Date rulesUpdatedAt;
+
+    public String getKey() {
+      return key;
+    }
+
+    public Builder setKey(String key) {
+      this.key = key;
+      return this;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public Builder setName(String name) {
+      this.name = name;
+      return this;
+    }
+
+    public String getLanguage() {
+      return language;
+    }
+
+    public Builder setLanguage(String language) {
+      this.language = language;
+      return this;
+    }
+
+    public Date getRulesUpdatedAt() {
+      return rulesUpdatedAt;
+    }
+
+    public Builder setRulesUpdatedAt(Date d) {
+      this.rulesUpdatedAt = d;
+      return this;
+    }
+
+    public QProfile build() {
+      return new QProfile(key, name, language, rulesUpdatedAt);
+    }
+  }
 }
index bd3d822bdec8b88de7528a5799064325fda6a729..2acf1e4e8f4a95451bd74086e30198c928b2ef19 100644 (file)
@@ -25,6 +25,8 @@ import java.util.Collections;
 import java.util.stream.Collectors;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
 import org.apache.commons.lang.builder.ReflectionToStringBuilder;
 import org.apache.commons.lang.builder.ToStringStyle;
 import org.sonar.api.batch.rule.Rules;
@@ -33,6 +35,7 @@ import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.rules.RuleQuery;
 
+@Immutable
 public class RuleFinderCompatibility implements RuleFinder {
 
   private final Rules rules;
index ae7f1de06ec2dc33da30ef8516cab455d29d8f04..4cf6dcbcab8385457efbeb24ec9d65bd36970b05 100644 (file)
  */
 package org.sonar.scanner.scan;
 
-import com.google.common.base.Preconditions;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
 import java.nio.file.Path;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+
 import javax.annotation.CheckForNull;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import javax.annotation.concurrent.Immutable;
+
 import org.sonar.api.batch.fs.InputModule;
 import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.api.scan.filesystem.PathResolver;
 
+import com.google.common.collect.ImmutableMultimap;
+
+@Immutable
 public class DefaultInputModuleHierarchy implements InputModuleHierarchy {
   private final PathResolver pathResolver = new PathResolver();
-  private DefaultInputModule root;
-  private final Map<DefaultInputModule, DefaultInputModule> parents = new HashMap<>();
-  private final Multimap<DefaultInputModule, DefaultInputModule> children = HashMultimap.create();
+  private final DefaultInputModule root;
+  private final Map<DefaultInputModule, DefaultInputModule> parents;
+  private final ImmutableMultimap<DefaultInputModule, DefaultInputModule> children;
 
-  public void setRoot(DefaultInputModule root) {
+  public DefaultInputModuleHierarchy(DefaultInputModule parent, DefaultInputModule child) {
+    this(Collections.singletonMap(child, parent));
+  }
+
+  public DefaultInputModuleHierarchy(DefaultInputModule root) {
+    this.children = new ImmutableMultimap.Builder<DefaultInputModule, DefaultInputModule>().build();
+    this.parents = Collections.emptyMap();
     this.root = root;
   }
 
-  public void index(DefaultInputModule child, DefaultInputModule parent) {
-    Preconditions.checkNotNull(child);
-    Preconditions.checkNotNull(parent);
-    parents.put(child, parent);
-    children.put(parent, child);
+  /**
+   * Map of child->parent. Neither the Keys or values can be null.
+   */
+  public DefaultInputModuleHierarchy(Map<DefaultInputModule, DefaultInputModule> parents) {
+    ImmutableMultimap.Builder<DefaultInputModule, DefaultInputModule> childrenBuilder = new ImmutableMultimap.Builder<>();
+
+    for (Map.Entry<DefaultInputModule, DefaultInputModule> e : parents.entrySet()) {
+      childrenBuilder.put(e.getValue(), e.getKey());
+    }
+
+    this.children = childrenBuilder.build();
+    this.parents = Collections.unmodifiableMap(new HashMap<>(parents));
+    this.root = findRoot(parents);
+  }
+
+  private static DefaultInputModule findRoot(Map<DefaultInputModule, DefaultInputModule> parents) {
+    DefaultInputModule r = null;
+    for (DefaultInputModule parent : parents.values()) {
+      if (!parents.containsKey(parent)) {
+        if (r != null && r != parent) {
+          throw new IllegalStateException(String.format("Found two modules without parent: '%s' and '%s'", r.key(), parent.key()));
+        }
+        r = parent;
+      }
+    }
+    if (r == null) {
+      throw new IllegalStateException("Found no root module");
+    }
+    return r;
   }
 
   @Override
@@ -78,11 +111,8 @@ public class DefaultInputModuleHierarchy implements InputModuleHierarchy {
       return null;
     }
     DefaultInputModule inputModule = (DefaultInputModule) module;
-
-    ProjectDefinition parentDefinition = parent.definition();
-    Path parentBaseDir = parentDefinition.getBaseDir().toPath();
-    ProjectDefinition moduleDefinition = inputModule.definition();
-    Path moduleBaseDir = moduleDefinition.getBaseDir().toPath();
+    Path parentBaseDir = parent.getBaseDir().toPath();
+    Path moduleBaseDir = inputModule.getBaseDir().toPath();
 
     return pathResolver.relativePath(parentBaseDir, moduleBaseDir);
   }
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ImmutableProjectReactor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ImmutableProjectReactor.java
deleted file mode 100644 (file)
index 6d5df79..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.scanner.scan;
-
-import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import javax.annotation.CheckForNull;
-import org.sonar.api.batch.ScannerSide;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
-
-/**
- * Immutable copy of project reactor after all modifications have been applied (see {@link ImmutableProjectReactorProvider}).
- */
-@ScannerSide
-public class ImmutableProjectReactor {
-
-  private ProjectDefinition root;
-  private Map<String, ProjectDefinition> byKey = new LinkedHashMap<>();
-
-  public ImmutableProjectReactor(ProjectDefinition root) {
-    if (root.getParent() != null) {
-      throw new IllegalArgumentException("Not a root project: " + root);
-    }
-    this.root = root;
-    collectProjects(root);
-  }
-
-  public Collection<ProjectDefinition> getProjects() {
-    return byKey.values();
-  }
-
-  /**
-   * Populates list of projects from hierarchy.
-   */
-  private void collectProjects(ProjectDefinition def) {
-    if (byKey.containsKey(def.getKeyWithBranch())) {
-      throw new IllegalStateException("Duplicate module key in reactor: " + def.getKeyWithBranch());
-    }
-    byKey.put(def.getKeyWithBranch(), def);
-    for (ProjectDefinition child : def.getSubProjects()) {
-      collectProjects(child);
-    }
-  }
-
-  public ProjectDefinition getRoot() {
-    return root;
-  }
-
-  @CheckForNull
-  public ProjectDefinition getProjectDefinition(String keyWithBranch) {
-    return byKey.get(keyWithBranch);
-  }
-}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ImmutableProjectReactorProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ImmutableProjectReactorProvider.java
deleted file mode 100644 (file)
index d3111db..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.scanner.scan;
-
-import org.picocontainer.injectors.ProviderAdapter;
-import org.sonar.api.batch.bootstrap.ProjectReactor;
-
-public class ImmutableProjectReactorProvider extends ProviderAdapter {
-
-  private ImmutableProjectReactor singleton;
-
-  public ImmutableProjectReactor provide(ProjectReactor reactor, ProjectBuildersExecutor projectBuildersExecutor, ProjectReactorValidator validator) {
-    if (singleton == null) {
-      // 1 Apply project builders
-      projectBuildersExecutor.execute(reactor);
-
-      // 2 Validate final reactor
-      validator.validate(reactor);
-
-      singleton = new ImmutableProjectReactor(reactor.getRoot());
-    }
-    return singleton;
-  }
-}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/InputModuleHierarchyProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/InputModuleHierarchyProvider.java
new file mode 100644 (file)
index 0000000..aaa8cf5
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.scan;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.picocontainer.injectors.ProviderAdapter;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
+import org.sonar.scanner.scan.filesystem.BatchIdGenerator;
+
+public class InputModuleHierarchyProvider extends ProviderAdapter {
+
+  private DefaultInputModuleHierarchy hierarchy = null;
+
+  public DefaultInputModuleHierarchy provide(ProjectBuildersExecutor projectBuildersExecutor, ProjectReactorValidator validator,
+    ProjectReactor projectReactor, BatchIdGenerator batchIdGenerator) {
+    if (hierarchy == null) {
+      // 1 Apply project builders
+      projectBuildersExecutor.execute(projectReactor);
+
+      // 2 Validate final reactor
+      validator.validate(projectReactor);
+
+      // 3 Create modules and the hierarchy
+      DefaultInputModule root = new DefaultInputModule(projectReactor.getRoot(), batchIdGenerator.get());
+      Map<DefaultInputModule, DefaultInputModule> parents = createChildren(root, batchIdGenerator, new HashMap<>());
+      if (parents.isEmpty()) {
+        hierarchy = new DefaultInputModuleHierarchy(root);
+      } else {
+        hierarchy = new DefaultInputModuleHierarchy(parents);
+      }
+    }
+    return hierarchy;
+  }
+
+  private static Map<DefaultInputModule, DefaultInputModule> createChildren(DefaultInputModule parent, BatchIdGenerator batchIdGenerator,
+    Map<DefaultInputModule, DefaultInputModule> parents) {
+    for (ProjectDefinition def : parent.definition().getSubProjects()) {
+      DefaultInputModule child = new DefaultInputModule(def, batchIdGenerator.get());
+      parents.put(child, parent);
+      createChildren(child, batchIdGenerator, parents);
+    }
+    return parents;
+  }
+}
index 5558d577ade6ae17d161e327c678d813c95f59bd..110baeb2287551a24a75022018b19cbd2b21687d 100644 (file)
@@ -20,9 +20,8 @@
 package org.sonar.scanner.scan;
 
 import org.picocontainer.Startable;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.batch.fs.internal.DefaultInputModule;
-import org.sonar.scanner.scan.filesystem.BatchIdGenerator;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.scanner.scan.filesystem.InputComponentStore;
 
 /**
@@ -30,36 +29,27 @@ import org.sonar.scanner.scan.filesystem.InputComponentStore;
  * project definitions provided by the {@link ImmutableProjectReactor}.
  */
 public class ModuleIndexer implements Startable {
-  private final ImmutableProjectReactor projectReactor;
   private final DefaultComponentTree componentTree;
-  private final DefaultInputModuleHierarchy moduleHierarchy;
-  private final BatchIdGenerator batchIdGenerator;
+  private final InputModuleHierarchy moduleHierarchy;
   private final InputComponentStore componentStore;
 
-  public ModuleIndexer(ImmutableProjectReactor projectReactor, DefaultComponentTree componentTree,
-    InputComponentStore componentStore, BatchIdGenerator batchIdGenerator, DefaultInputModuleHierarchy moduleHierarchy) {
-    this.projectReactor = projectReactor;
+  public ModuleIndexer(DefaultComponentTree componentTree, InputComponentStore componentStore, InputModuleHierarchy moduleHierarchy) {
     this.componentTree = componentTree;
     this.componentStore = componentStore;
     this.moduleHierarchy = moduleHierarchy;
-    this.batchIdGenerator = batchIdGenerator;
   }
 
   @Override
   public void start() {
-    DefaultInputModule root = new DefaultInputModule(projectReactor.getRoot(), batchIdGenerator.get());
-    moduleHierarchy.setRoot(root);
-    componentStore.put(root);
-    createChildren(root);
+    DefaultInputModule root = moduleHierarchy.root();
+    indexChildren(root);
   }
 
-  private void createChildren(DefaultInputModule parent) {
-    for (ProjectDefinition def : parent.definition().getSubProjects()) {
-      DefaultInputModule child = new DefaultInputModule(def, batchIdGenerator.get());
-      moduleHierarchy.index(child, parent);
-      componentTree.index(child, parent);
-      componentStore.put(child);
-      createChildren(child);
+  private void indexChildren(DefaultInputModule parent) {
+    for (DefaultInputModule module : moduleHierarchy.children(parent)) {
+      componentTree.index(module, parent);
+      componentStore.put(module);
+      indexChildren(module);
     }
   }
 
index 8305472e8aa4b57e1adc16eaad5cb5a69ec121a6..dbb3862d8319a6923538bd0df958970f1229b1e8 100644 (file)
@@ -95,7 +95,7 @@ public class ModuleScanContainer extends ComponentContainer {
     add(
       module.definition(),
       // still injected by some plugins
-      new Project(module.definition()),
+      new Project(module),
       module,
       MutableModuleSettings.class,
       new ModuleSettingsProvider());
index d17a9c0916f44c00f90f4c54174a5b8f54b0cafc..25db68aad37aa1ad5a63102e0b97c28d1e6ff4ca 100644 (file)
@@ -27,6 +27,7 @@ import java.util.Map;
 import org.picocontainer.injectors.ProviderAdapter;
 import org.sonar.api.batch.AnalysisMode;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.scanner.bootstrap.GlobalConfiguration;
 import org.sonar.scanner.report.AnalysisContextReportPublisher;
 import org.sonar.scanner.repository.ProjectRepositories;
@@ -35,15 +36,15 @@ public class ModuleSettingsProvider extends ProviderAdapter {
 
   private ModuleSettings projectSettings;
 
-  public ModuleSettings provide(GlobalConfiguration globalSettings, ProjectDefinition moduleDefinition, ProjectRepositories projectRepos,
+  public ModuleSettings provide(GlobalConfiguration globalSettings, DefaultInputModule module, ProjectRepositories projectRepos,
     AnalysisMode analysisMode, AnalysisContextReportPublisher contextReportPublisher) {
     if (projectSettings == null) {
 
       Map<String, String> settings = new LinkedHashMap<>();
       settings.putAll(globalSettings.getProperties());
-      settings.putAll(addServerSidePropertiesIfModuleExists(projectRepos, moduleDefinition));
-      addScannerSideProperties(settings, moduleDefinition);
-      contextReportPublisher.dumpModuleSettings(moduleDefinition);
+      settings.putAll(addServerSidePropertiesIfModuleExists(projectRepos, module.definition()));
+      addScannerSideProperties(settings, module.definition());
+      contextReportPublisher.dumpModuleSettings(module);
 
       projectSettings = new ModuleSettings(globalSettings.getDefinitions(), globalSettings.getEncryption(), analysisMode, settings);
     }
index bddf206cc599d88ab7e0efbf241bfcea5056ab7f..d8af158590dc9a171bf553ae734e0aefbd666700 100644 (file)
@@ -23,16 +23,17 @@ import java.io.IOException;
 import java.nio.channels.OverlappingFileLockException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+
 import org.picocontainer.Startable;
-import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.home.cache.DirectoryLock;
 import org.sonar.scanner.bootstrap.Slf4jLogger;
 
 public class ProjectLock implements Startable {
   private final DirectoryLock lock;
 
-  public ProjectLock(ProjectReactor projectReactor) {
-    Path directory = projectReactor.getRoot().getWorkDir().toPath();
+  public ProjectLock(InputModuleHierarchy moduleHierarchy) {
+    Path directory = moduleHierarchy.root().getWorkDir().toPath();
     try {
       if (!directory.toFile().exists()) {
         Files.createDirectories(directory);
index f5f9d0886e7bd2c849bebb96744dc72b312ab89b..e69eb31916e26bcf5575a3791dae904611a4d997 100644 (file)
  */
 package org.sonar.scanner.scan;
 
-import com.google.common.base.Joiner;
 import java.util.ArrayList;
 import java.util.List;
+
 import javax.annotation.Nullable;
+
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.batch.bootstrap.ProjectReactor;
-import org.sonar.api.config.Settings;
 import org.sonar.api.utils.MessageException;
 import org.sonar.core.component.ComponentKeys;
 import org.sonar.scanner.analysis.DefaultAnalysisMode;
 
+import com.google.common.base.Joiner;
+
 /**
  * This class aims at validating project reactor
  * @since 3.6
  */
 public class ProjectReactorValidator {
-
-  private static final String SONAR_PHASE = "sonar.phase";
-  private final Settings settings;
   private final DefaultAnalysisMode mode;
 
-  public ProjectReactorValidator(Settings settings, DefaultAnalysisMode mode) {
-    this.settings = settings;
+  public ProjectReactorValidator(DefaultAnalysisMode mode) {
     this.mode = mode;
   }
 
@@ -50,7 +48,6 @@ public class ProjectReactorValidator {
     String branch = reactor.getRoot().getBranch();
 
     List<String> validationMessages = new ArrayList<>();
-    checkDeprecatedProperties(validationMessages);
 
     for (ProjectDefinition moduleDef : reactor.getProjects()) {
       if (mode.isIssues()) {
@@ -81,12 +78,6 @@ public class ProjectReactorValidator {
     }
   }
 
-  private void checkDeprecatedProperties(List<String> validationMessages) {
-    if (settings.getString(SONAR_PHASE) != null) {
-      validationMessages.add(String.format("Property \"%s\" is deprecated. Please remove it from your configuration.", SONAR_PHASE));
-    }
-  }
-
   private static void validateBranch(List<String> validationMessages, @Nullable String branch) {
     if (StringUtils.isNotEmpty(branch) && !ComponentKeys.isValidBranch(branch)) {
       validationMessages.add(String.format("\"%s\" is not a valid branch name. "
index 9720666b11342588748cf92245e043592c7e94d9..458239cc526ffb799c5e4b2e98834b6e7c92490b 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.scanner.scan;
 
-import com.google.common.annotations.VisibleForTesting;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.InstantiationStrategy;
@@ -42,6 +41,7 @@ import org.sonar.scanner.bootstrap.ExtensionMatcher;
 import org.sonar.scanner.bootstrap.ExtensionUtils;
 import org.sonar.scanner.bootstrap.MetricProvider;
 import org.sonar.scanner.cpd.CpdExecutor;
+import org.sonar.scanner.cpd.CpdSettings;
 import org.sonar.scanner.cpd.index.SonarCpdBlockIndex;
 import org.sonar.scanner.deprecated.test.TestPlanBuilder;
 import org.sonar.scanner.deprecated.test.TestableBuilder;
@@ -86,12 +86,14 @@ import org.sonar.scanner.rule.DefaultRulesLoader;
 import org.sonar.scanner.rule.RulesLoader;
 import org.sonar.scanner.rule.RulesProvider;
 import org.sonar.scanner.scan.filesystem.BatchIdGenerator;
-import org.sonar.scanner.scan.filesystem.InputComponentStore;
+import org.sonar.scanner.scan.filesystem.InputComponentStoreProvider;
 import org.sonar.scanner.scan.measure.DefaultMetricFinder;
 import org.sonar.scanner.scan.measure.DeprecatedMetricFinder;
 import org.sonar.scanner.scan.measure.MeasureCache;
 import org.sonar.scanner.storage.Storages;
 
+import com.google.common.annotations.VisibleForTesting;
+
 public class ProjectScanContainer extends ComponentContainer {
 
   private static final Logger LOG = Loggers.get(ProjectScanContainer.class);
@@ -106,10 +108,10 @@ public class ProjectScanContainer extends ComponentContainer {
   @Override
   protected void doBeforeStart() {
     addBatchComponents();
+    addBatchExtensions();
     ProjectLock lock = getComponentByType(ProjectLock.class);
     lock.tryLock();
     getComponentByType(WorkDirectoryCleaner.class).execute();
-    addBatchExtensions();
     Settings settings = getComponentByType(Settings.class);
     if (settings != null && settings.getBoolean(CoreProperties.PROFILING_LOG_PROPERTY)) {
       add(PhasesSumUpTimeProfiler.class);
@@ -126,7 +128,6 @@ public class ProjectScanContainer extends ComponentContainer {
       ProjectReactorBuilder.class,
       WorkDirectoryCleaner.class,
       new MutableProjectReactorProvider(),
-      new ImmutableProjectReactorProvider(),
       ProjectBuildersExecutor.class,
       ProjectLock.class,
       EventBus.class,
@@ -145,9 +146,9 @@ public class ProjectScanContainer extends ComponentContainer {
 
       // file system
       ModuleIndexer.class,
-      InputComponentStore.class,
+      new InputComponentStoreProvider(),
       PathResolver.class,
-      DefaultInputModuleHierarchy.class,
+      new InputModuleHierarchyProvider(),
       DefaultComponentTree.class,
       BatchIdGenerator.class,
 
@@ -197,6 +198,7 @@ public class ProjectScanContainer extends ComponentContainer {
 
       // Cpd
       CpdExecutor.class,
+      CpdSettings.class,
       SonarCpdBlockIndex.class,
 
       ScanTaskObservers.class);
index 607b7bb0397c6c0cf5a43f798f1b010fcb9d804e..277b230fd88973d3eccc24a80cc422ac275c576b 100644 (file)
@@ -24,15 +24,16 @@ import java.nio.file.DirectoryStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Iterator;
-import org.sonar.api.batch.bootstrap.ProjectReactor;
+
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.core.util.FileUtils;
 import org.sonar.home.cache.DirectoryLock;
 
 public class WorkDirectoryCleaner {
-  private Path workDir;
+  private final Path workDir;
 
-  public WorkDirectoryCleaner(ProjectReactor projectReactor) {
-    workDir = projectReactor.getRoot().getWorkDir().toPath();
+  public WorkDirectoryCleaner(InputModuleHierarchy moduleHierarchy) {
+    workDir = moduleHierarchy.root().getWorkDir().toPath();
   }
 
   public void execute() {
index 087f8445ad32c2d9a2bf1917bf94095310b94979..119d8d9bc9f97c5c21d0446928caa4f19f524ea8 100644 (file)
@@ -21,6 +21,9 @@ package org.sonar.scanner.scan.filesystem;
 
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Supplier;
+
+import javax.annotation.concurrent.ThreadSafe;
+
 import org.sonar.api.batch.fs.InputComponent;
 
 /**
@@ -28,6 +31,7 @@ import org.sonar.api.batch.fs.InputComponent;
  * The IDs must be unique among all types of components and for all modules in the project.
  * The ID should never be 0, as it is sometimes used to indicate invalid components. 
  */
+@ThreadSafe
 public class BatchIdGenerator implements Supplier<Integer> {
   private AtomicInteger nextBatchId = new AtomicInteger(1);
 
index 0bebac556363ca70182f8f8b2dbbcd0ba9acbe68..f84bec040cf897cf35731e886ddb073a00c16876 100644 (file)
@@ -84,7 +84,7 @@ public class ExclusionFilters {
     if (inclusionPatterns.length > 0) {
       boolean matchInclusion = false;
       for (PathPattern pattern : inclusionPatterns) {
-        matchInclusion |= pattern.match(indexedFile);
+        matchInclusion |= pattern.match(indexedFile.absolutePath(), indexedFile.relativePath());
       }
       if (!matchInclusion) {
         return false;
@@ -92,7 +92,7 @@ public class ExclusionFilters {
     }
     if (exclusionPatterns.length > 0) {
       for (PathPattern pattern : exclusionPatterns) {
-        if (pattern.match(indexedFile)) {
+        if (pattern.match(indexedFile.absolutePath(), indexedFile.relativePath())) {
           return false;
         }
       }
index dd8a32e91e587dad442dad1c1dfc57bd2de6a2f8..3af3741e987b61f229d645ddd238d6393b742a88 100644 (file)
@@ -157,9 +157,10 @@ public class FileIndexer {
     DefaultInputFile inputFile = inputFileBuilder.create(realFile, type, fileSystem.encoding());
     if (inputFile != null) {
       if (exclusionFilters.accept(inputFile, type) && accept(inputFile)) {
+        String parentRelativePath = getParentRelativePath(fileSystem, inputFile);
         synchronized (this) {
           fileSystem.add(inputFile);
-          indexParentDir(fileSystem, inputFile);
+          indexParentDir(fileSystem, inputFile, parentRelativePath);
           progress.markAsIndexed(inputFile);
         }
         LOG.debug("'{}' indexed {}with language '{}'", inputFile.relativePath(), type == Type.TEST ? "as test " : "", inputFile.language());
@@ -171,16 +172,19 @@ public class FileIndexer {
     return null;
   }
 
-  private void indexParentDir(DefaultModuleFileSystem fileSystem, InputFile inputFile) {
+  private static String getParentRelativePath(DefaultModuleFileSystem fileSystem, InputFile inputFile) {
     Path parentDir = inputFile.path().getParent();
     String relativePath = new PathResolver().relativePath(fileSystem.baseDirPath(), parentDir);
     if (relativePath == null) {
       throw new IllegalStateException("Failed to compute relative path of file: " + inputFile);
     }
+    return relativePath;
+  }
 
-    DefaultInputDir inputDir = (DefaultInputDir) componentStore.getDir(module.key(), relativePath);
+  private void indexParentDir(DefaultModuleFileSystem fileSystem, InputFile inputFile, String parentRelativePath) {
+    DefaultInputDir inputDir = (DefaultInputDir) componentStore.getDir(module.key(), parentRelativePath);
     if (inputDir == null) {
-      inputDir = new DefaultInputDir(fileSystem.moduleKey(), relativePath, batchIdGenerator.get());
+      inputDir = new DefaultInputDir(fileSystem.moduleKey(), parentRelativePath, batchIdGenerator.get());
       inputDir.setModuleBaseDir(fileSystem.baseDirPath());
       fileSystem.add(inputDir);
       componentTree.index(inputDir, module);
index ff92762a4059775e7f56a16c964322d8bdbde8bd..6726cba970b43f313300c2aa837328ef13acec65 100644 (file)
@@ -58,14 +58,17 @@ public class InputComponentStore {
   private final Table<String, String, InputFile> inputFileCache = TreeBasedTable.create();
   private final Map<String, InputDir> globalInputDirCache = new HashMap<>();
   private final Table<String, String, InputDir> inputDirCache = TreeBasedTable.create();
+  // indexed by key with branch
   private final Map<String, InputModule> inputModuleCache = new HashMap<>();
   private final Map<String, InputComponent> inputComponents = new HashMap<>();
   private final SetMultimap<String, InputFile> filesByNameCache = LinkedHashMultimap.create();
   private final SetMultimap<String, InputFile> filesByExtensionCache = LinkedHashMultimap.create();
-  private InputModule root;
+  private final InputModule root;
 
-  public InputComponentStore(PathResolver pathResolver) {
+  public InputComponentStore(PathResolver pathResolver, DefaultInputModule root) {
     this.pathResolver = pathResolver;
+    this.root = root;
+    this.put(root);
   }
 
   public Collection<InputComponent> all() {
@@ -90,7 +93,6 @@ public class InputComponentStore {
     return inputComponents.get(key);
   }
 
-  @CheckForNull
   public InputModule root() {
     return root;
   }
@@ -157,7 +159,7 @@ public class InputComponentStore {
   }
 
   private Path getProjectBaseDir() {
-    return ((DefaultInputModule) root).definition().getBaseDir().toPath();
+    return ((DefaultInputModule) root).getBaseDir().toPath();
   }
 
   @CheckForNull
@@ -181,22 +183,18 @@ public class InputComponentStore {
   }
 
   @CheckForNull
-  public InputModule getModule(String moduleKey) {
-    return inputModuleCache.get(moduleKey);
+  public InputModule getModule(String moduleKeyWithBranch) {
+    return inputModuleCache.get(moduleKeyWithBranch);
   }
 
   public void put(DefaultInputModule inputModule) {
     String key = inputModule.key();
+    String keyWithBranch = inputModule.getKeyWithBranch();
+    Preconditions.checkNotNull(inputModule);
     Preconditions.checkState(!inputComponents.containsKey(key), "Module '%s' already indexed", key);
-    Preconditions.checkState(!inputModuleCache.containsKey(key), "Module '%s' already indexed", key);
+    Preconditions.checkState(!inputModuleCache.containsKey(keyWithBranch), "Module '%s' already indexed", keyWithBranch);
     inputComponents.put(key, inputModule);
-    inputModuleCache.put(key, inputModule);
-    if (inputModule.definition().getParent() == null) {
-      if (root != null) {
-        throw new IllegalStateException("Root module already indexed: '" + root.key() + "', '" + key + "'");
-      }
-      root = inputModule;
-    }
+    inputModuleCache.put(keyWithBranch, inputModule);
   }
 
   public Iterable<InputFile> getFilesByName(String filename) {
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/InputComponentStoreProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/InputComponentStoreProvider.java
new file mode 100644 (file)
index 0000000..65e78e6
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.scan.filesystem;
+
+import org.picocontainer.injectors.ProviderAdapter;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
+import org.sonar.api.scan.filesystem.PathResolver;
+
+public class InputComponentStoreProvider extends ProviderAdapter {
+  private InputComponentStore store;
+
+  public InputComponentStore provide(PathResolver pathResolver, InputModuleHierarchy hierarchy) {
+    if (store == null) {
+      store = new InputComponentStore(pathResolver, hierarchy.root());
+    }
+    return store;
+  }
+}
index c44e24f4942fbf442055d8311ac7ae38b6e81d3a..fe8d125a5e9640e0b0c3eeb845de3f23f4b81cb5 100644 (file)
@@ -60,14 +60,13 @@ public class InputFileBuilder {
       LOG.warn("File '{}' is ignored. It is not located in module basedir '{}'.", file.toAbsolutePath(), moduleBaseDir);
       return null;
     }
-    DefaultIndexedFile indexedFile = new DefaultIndexedFile(moduleKey, moduleBaseDir, relativePath, type, idGenerator.get());
-    String language = langDetection.language(indexedFile);
+    String language = langDetection.language(file.toAbsolutePath().normalize().toString(), relativePath);
     if (language == null && langDetection.forcedLanguage() != null) {
       LOG.warn("File '{}' is ignored because it doesn't belong to the forced language '{}'", file.toAbsolutePath(), langDetection.forcedLanguage());
       return null;
     }
-    indexedFile.setLanguage(language);
 
+    DefaultIndexedFile indexedFile = new DefaultIndexedFile(moduleKey, moduleBaseDir, relativePath, type, language, idGenerator.get());
     DefaultInputFile inputFile = new DefaultInputFile(indexedFile, f -> metadataGenerator.setMetadata(f, defaultEncoding));
     if (language != null) {
       inputFile.setPublish(true);
index 828a8403655572096ff1906327b59417bf57ac76..8267e4509c9e6f69130266b5edba5766f7ad3922 100644 (file)
  */
 package org.sonar.scanner.scan.filesystem;
 
-import com.google.common.base.Joiner;
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+
 import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.ThreadSafe;
+
 import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.ScannerSide;
-import org.sonar.api.batch.fs.internal.DefaultIndexedFile;
 import org.sonar.api.batch.fs.internal.PathPattern;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.utils.MessageException;
 import org.sonar.scanner.repository.language.Language;
 import org.sonar.scanner.repository.language.LanguagesRepository;
 
+import com.google.common.base.Joiner;
+
 /**
  * Detect language of a source file based on its suffix and configured patterns.
  */
 @ScannerSide
+@ThreadSafe
 public class LanguageDetection {
 
   private static final Logger LOG = LoggerFactory.getLogger(LanguageDetection.class);
@@ -49,16 +54,17 @@ public class LanguageDetection {
   /**
    * Lower-case extension -> languages
    */
-  private final Map<String, PathPattern[]> patternsByLanguage = new LinkedHashMap<>();
-  private final List<String> languagesToConsider = new ArrayList<>();
+  private final Map<String, PathPattern[]> patternsByLanguage;
+  private final List<String> languagesToConsider;
   private final String forcedLanguage;
 
   public LanguageDetection(Configuration settings, LanguagesRepository languages) {
+    Map<String, PathPattern[]> patternsByLanguageBuilder = new LinkedHashMap<>();
     for (Language language : languages.all()) {
       String[] filePatterns = settings.getStringArray(getFileLangPatternPropKey(language.key()));
       PathPattern[] pathPatterns = PathPattern.create(filePatterns);
       if (pathPatterns.length > 0) {
-        patternsByLanguage.put(language.key(), pathPatterns);
+        patternsByLanguageBuilder.put(language.key(), pathPatterns);
       } else {
         // If no custom language pattern is defined then fallback to suffixes declared by language
         String[] patterns = language.fileSuffixes().toArray(new String[language.fileSuffixes().size()]);
@@ -68,22 +74,24 @@ public class LanguageDetection {
           patterns[i] = new StringBuilder().append("**/*.").append(extension).toString();
         }
         PathPattern[] defaultLanguagePatterns = PathPattern.create(patterns);
-        patternsByLanguage.put(language.key(), defaultLanguagePatterns);
-        LOG.debug("Declared extensions of language {} were converted to {}", language, getDetails(language.key()));
+        patternsByLanguageBuilder.put(language.key(), defaultLanguagePatterns);
+        LOG.debug("Declared extensions of language {} were converted to {}", language, getDetails(language.key(), defaultLanguagePatterns));
       }
     }
 
     forcedLanguage = StringUtils.defaultIfBlank(settings.get(CoreProperties.PROJECT_LANGUAGE_PROPERTY).orElse(null), null);
     // First try with lang patterns
     if (forcedLanguage != null) {
-      if (!patternsByLanguage.containsKey(forcedLanguage)) {
+      if (!patternsByLanguageBuilder.containsKey(forcedLanguage)) {
         throw MessageException.of("You must install a plugin that supports the language '" + forcedLanguage + "'");
       }
       LOG.info("Language is forced to {}", forcedLanguage);
-      languagesToConsider.add(forcedLanguage);
+      languagesToConsider = Collections.singletonList(forcedLanguage);
     } else {
-      languagesToConsider.addAll(patternsByLanguage.keySet());
+      languagesToConsider = Collections.unmodifiableList(new ArrayList<>(patternsByLanguageBuilder.keySet()));
     }
+
+    patternsByLanguage = Collections.unmodifiableMap(patternsByLanguageBuilder);
   }
 
   public String forcedLanguage() {
@@ -95,16 +103,16 @@ public class LanguageDetection {
   }
 
   @CheckForNull
-  String language(DefaultIndexedFile inputFile) {
+  String language(String absolutePath, String relativePath) {
     String detectedLanguage = null;
     for (String languageKey : languagesToConsider) {
-      if (isCandidateForLanguage(inputFile, languageKey)) {
+      if (isCandidateForLanguage(absolutePath, relativePath, languageKey)) {
         if (detectedLanguage == null) {
           detectedLanguage = languageKey;
         } else {
           // Language was already forced by another pattern
           throw MessageException.of(MessageFormat.format("Language of file ''{0}'' can not be decided as the file matches patterns of both {1} and {2}",
-            inputFile.relativePath(), getDetails(detectedLanguage), getDetails(languageKey)));
+            relativePath, getDetails(detectedLanguage), getDetails(languageKey)));
         }
       }
     }
@@ -120,11 +128,11 @@ public class LanguageDetection {
     return null;
   }
 
-  private boolean isCandidateForLanguage(DefaultIndexedFile inputFile, String languageKey) {
+  private boolean isCandidateForLanguage(String absolutePath, String relativePath, String languageKey) {
     PathPattern[] patterns = patternsByLanguage.get(languageKey);
     if (patterns != null) {
       for (PathPattern pathPattern : patterns) {
-        if (pathPattern.match(inputFile, false)) {
+        if (pathPattern.match(absolutePath, relativePath, false)) {
           return true;
         }
       }
@@ -137,7 +145,11 @@ public class LanguageDetection {
   }
 
   private String getDetails(String detectedLanguage) {
-    return getFileLangPatternPropKey(detectedLanguage) + " : " + Joiner.on(",").join(patternsByLanguage.get(detectedLanguage));
+    return getDetails(detectedLanguage, patternsByLanguage.get(detectedLanguage));
+  }
+
+  private static String getDetails(String detectedLanguage, PathPattern[] patterns) {
+    return getFileLangPatternPropKey(detectedLanguage) + " : " + Joiner.on(",").join(patterns);
   }
 
   static String sanitizeExtension(String suffix) {
index 2c5c9e54e8dfd68fc0e4ce12f217dcabf8f0a4f5..f13b39b5325ed0e4d4e7009409b8c4bac92d483c 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.scanner.scan.filesystem;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
+
 import org.apache.commons.io.FileUtils;
 import org.sonar.api.batch.ScannerSide;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
index 851332f663d4a799a1a6d5738b1a907da1d0236c..ac11c11d3b81825ddbfe0225cf1a1018a5000217 100644 (file)
  */
 package org.sonar.scanner.scan.filesystem;
 
+import javax.annotation.concurrent.Immutable;
+
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.scanner.repository.FileData;
 import org.sonar.scanner.repository.ProjectRepositories;
 
+@Immutable
 class StatusDetection {
 
   private final ProjectRepositories projectSettings;
index 00d484ef59824f8fc926070ac43c31992d69add3..c6655b7d570459e1641b32f480404abca86b56be 100644 (file)
@@ -22,21 +22,28 @@ package org.sonar.scanner.scan.measure;
 import com.google.common.collect.Lists;
 import java.io.Serializable;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+
+import javax.annotation.concurrent.ThreadSafe;
+
 import org.sonar.api.batch.measure.Metric;
 import org.sonar.api.batch.measure.MetricFinder;
 import org.sonar.scanner.repository.MetricsRepository;
 
+@ThreadSafe
 public class DefaultMetricFinder implements MetricFinder {
 
-  private Map<String, Metric<Serializable>> metricsByKey = new LinkedHashMap<>();
+  private Map<String, Metric<Serializable>> metricsByKey;
 
   public DefaultMetricFinder(MetricsRepository metricsRepository) {
+    Map<String, Metric<Serializable>> metrics = new LinkedHashMap<>();
     for (org.sonar.api.measures.Metric metric : metricsRepository.metrics()) {
-      metricsByKey.put(metric.key(), new org.sonar.api.measures.Metric.Builder(metric.key(), metric.key(), metric.getType()).create());
+      metrics.put(metric.key(), new org.sonar.api.measures.Metric.Builder(metric.key(), metric.key(), metric.getType()).create());
     }
+    metricsByKey = Collections.unmodifiableMap(metrics);
   }
 
   @Override
index eaaf9b00b0e1d0f1949fdc1bc6638ee5634048b8..46531e99c405c7842e49cf5aa02d6a271e9b77b1 100644 (file)
@@ -20,6 +20,8 @@
 package org.sonar.scanner.scan.report;
 
 import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.Immutable;
+
 import org.apache.commons.lang.StringEscapeUtils;
 import org.sonar.api.batch.ScannerSide;
 import org.sonar.api.batch.rule.Rule;
@@ -27,6 +29,7 @@ import org.sonar.api.batch.rule.Rules;
 import org.sonar.api.rule.RuleKey;
 
 @ScannerSide
+@Immutable
 public class RuleNameProvider {
   private Rules rules;
 
index 68c75b647a6a80ad7036f4693056259fcbae2afb..73f29557bc0b2af79d9d42e045976c7219971b19 100644 (file)
@@ -19,9 +19,9 @@
  */
 package org.sonar.scanner.scm;
 
-import com.google.common.base.Joiner;
 import java.util.LinkedHashMap;
 import java.util.Map;
+
 import org.apache.commons.lang.StringUtils;
 import org.picocontainer.Startable;
 import org.sonar.api.CoreProperties;
@@ -31,11 +31,13 @@ import org.sonar.api.PropertyType;
 import org.sonar.api.batch.AnalysisMode;
 import org.sonar.api.batch.InstantiationStrategy;
 import org.sonar.api.batch.ScannerSide;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.api.batch.scm.ScmProvider;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
-import org.sonar.scanner.scan.ImmutableProjectReactor;
+
+import com.google.common.base.Joiner;
 
 @Properties({
   @Property(
@@ -56,15 +58,15 @@ public final class ScmConfiguration implements Startable {
 
   public static final String FORCE_RELOAD_KEY = "sonar.scm.forceReloadAll";
 
-  private final ImmutableProjectReactor projectReactor;
   private final Configuration settings;
   private final Map<String, ScmProvider> providerPerKey = new LinkedHashMap<>();
   private final AnalysisMode analysisMode;
+  private final InputModuleHierarchy moduleHierarchy;
 
   private ScmProvider provider;
 
-  public ScmConfiguration(ImmutableProjectReactor projectReactor, AnalysisMode analysisMode, Configuration settings, ScmProvider... providers) {
-    this.projectReactor = projectReactor;
+  public ScmConfiguration(InputModuleHierarchy moduleHierarchy, AnalysisMode analysisMode, Configuration settings, ScmProvider... providers) {
+    this.moduleHierarchy = moduleHierarchy;
     this.analysisMode = analysisMode;
     this.settings = settings;
     for (ScmProvider scmProvider : providers) {
@@ -72,8 +74,8 @@ public final class ScmConfiguration implements Startable {
     }
   }
 
-  public ScmConfiguration(ImmutableProjectReactor projectReactor, AnalysisMode analysisMode, Configuration settings) {
-    this(projectReactor, analysisMode, settings, new ScmProvider[0]);
+  public ScmConfiguration(InputModuleHierarchy moduleHierarchy, AnalysisMode analysisMode, Configuration settings) {
+    this(moduleHierarchy, analysisMode, settings, new ScmProvider[0]);
   }
 
   @Override
@@ -121,7 +123,7 @@ public final class ScmConfiguration implements Startable {
 
   private void autodetection() {
     for (ScmProvider installedProvider : providerPerKey.values()) {
-      if (installedProvider.supports(projectReactor.getRoot().getBaseDir())) {
+      if (installedProvider.supports(moduleHierarchy.root().getBaseDir())) {
         if (this.provider == null) {
           this.provider = installedProvider;
         } else {
index 9467d96829a18159e318453e79e5576321a99b67..1889bbaab94abbd08bd7ea89343952246c31a1df 100644 (file)
@@ -20,6 +20,9 @@
 package org.sonar.scanner.sensor;
 
 import java.io.Serializable;
+
+import javax.annotation.concurrent.ThreadSafe;
+
 import org.sonar.api.SonarRuntime;
 import org.sonar.api.batch.AnalysisMode;
 import org.sonar.api.batch.fs.FileSystem;
@@ -50,6 +53,7 @@ import org.sonar.scanner.sensor.noop.NoOpNewCpdTokens;
 import org.sonar.scanner.sensor.noop.NoOpNewHighlighting;
 import org.sonar.scanner.sensor.noop.NoOpNewSymbolTable;
 
+@ThreadSafe
 public class DefaultSensorContext implements SensorContext {
 
   private static final NoOpNewHighlighting NO_OP_NEW_HIGHLIGHTING = new NoOpNewHighlighting();
index ac966c2a855da33d15b7c70ee731ce3a5ee93a9b..c6fc08c8ce4b691b858bed37b7718d51c312fd43 100644 (file)
  */
 package org.sonar.scanner.sensor;
 
-import com.google.common.annotations.VisibleForTesting;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.sonar.api.batch.fs.InputComponent;
-import org.sonar.api.batch.fs.InputFile;
-import org.sonar.api.batch.fs.TextRange;
-import org.sonar.api.batch.fs.internal.DefaultInputFile;
-import org.sonar.api.batch.measure.Metric;
-import org.sonar.api.batch.measure.MetricFinder;
-import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage;
-import org.sonar.api.batch.sensor.cpd.internal.DefaultCpdTokens;
-import org.sonar.api.batch.sensor.error.AnalysisError;
-import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting;
-import org.sonar.api.batch.sensor.internal.SensorStorage;
-import org.sonar.api.batch.sensor.issue.Issue;
-import org.sonar.api.batch.sensor.measure.Measure;
-import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
-import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.utils.KeyValueFormat;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.core.metric.ScannerMetrics;
-import org.sonar.duplications.block.Block;
-import org.sonar.duplications.internal.pmd.PmdBlockChunker;
-import org.sonar.scanner.cpd.deprecated.DefaultCpdBlockIndexer;
-import org.sonar.scanner.cpd.index.SonarCpdBlockIndex;
-import org.sonar.scanner.issue.ModuleIssues;
-import org.sonar.scanner.protocol.output.FileStructure;
-import org.sonar.scanner.protocol.output.ScannerReport;
-import org.sonar.scanner.protocol.output.ScannerReportWriter;
-import org.sonar.scanner.report.ReportPublisher;
-import org.sonar.scanner.report.ScannerReportUtils;
-import org.sonar.scanner.repository.ContextPropertiesCache;
-import org.sonar.scanner.scan.measure.MeasureCache;
-import org.sonar.scanner.sensor.coverage.CoverageExclusions;
-
 import static java.util.stream.Collectors.toList;
 import static org.sonar.api.measures.CoreMetrics.BRANCH_COVERAGE;
 import static org.sonar.api.measures.CoreMetrics.COMMENTED_OUT_CODE_LINES_KEY;
@@ -112,6 +66,54 @@ import static org.sonar.api.measures.CoreMetrics.TEST_SUCCESS_DENSITY_KEY;
 import static org.sonar.api.measures.CoreMetrics.UNCOVERED_CONDITIONS;
 import static org.sonar.api.measures.CoreMetrics.UNCOVERED_LINES;
 
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.sonar.api.batch.fs.InputComponent;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.TextRange;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.measure.Metric;
+import org.sonar.api.batch.measure.MetricFinder;
+import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage;
+import org.sonar.api.batch.sensor.cpd.internal.DefaultCpdTokens;
+import org.sonar.api.batch.sensor.error.AnalysisError;
+import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting;
+import org.sonar.api.batch.sensor.internal.SensorStorage;
+import org.sonar.api.batch.sensor.issue.Issue;
+import org.sonar.api.batch.sensor.measure.Measure;
+import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
+import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.utils.KeyValueFormat;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.metric.ScannerMetrics;
+import org.sonar.duplications.block.Block;
+import org.sonar.duplications.internal.pmd.PmdBlockChunker;
+import org.sonar.scanner.cpd.deprecated.DefaultCpdBlockIndexer;
+import org.sonar.scanner.cpd.index.SonarCpdBlockIndex;
+import org.sonar.scanner.issue.ModuleIssues;
+import org.sonar.scanner.protocol.output.FileStructure;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.scanner.protocol.output.ScannerReportWriter;
+import org.sonar.scanner.report.ReportPublisher;
+import org.sonar.scanner.report.ScannerReportUtils;
+import org.sonar.scanner.repository.ContextPropertiesCache;
+import org.sonar.scanner.scan.measure.MeasureCache;
+import org.sonar.scanner.sensor.coverage.CoverageExclusions;
+
+import com.google.common.annotations.VisibleForTesting;
+
 public class DefaultSensorStorage implements SensorStorage {
 
   private static final Logger LOG = Loggers.get(DefaultSensorStorage.class);
@@ -151,12 +153,10 @@ public class DefaultSensorStorage implements SensorStorage {
   private final Map<Metric<?>, Metric<?>> deprecatedCoverageMetricMapping = new HashMap<>();
   private final Set<Metric<?>> coverageMetrics = new HashSet<>();
   private final Set<Metric<?>> byLineMetrics = new HashSet<>();
-  private Set<String> alreadyLogged = new HashSet<>();
+  private final Set<String> alreadyLogged = new HashSet<>();
 
-  public DefaultSensorStorage(MetricFinder metricFinder, ModuleIssues moduleIssues,
-    Configuration settings,
-    CoverageExclusions coverageExclusions, ReportPublisher reportPublisher,
-    MeasureCache measureCache, SonarCpdBlockIndex index,
+  public DefaultSensorStorage(MetricFinder metricFinder, ModuleIssues moduleIssues, Configuration settings, CoverageExclusions coverageExclusions,
+    ReportPublisher reportPublisher, MeasureCache measureCache, SonarCpdBlockIndex index,
     ContextPropertiesCache contextPropertiesCache, ScannerMetrics scannerMetrics) {
     this.metricFinder = metricFinder;
     this.moduleIssues = moduleIssues;
@@ -210,10 +210,12 @@ public class DefaultSensorStorage implements SensorStorage {
     saveMeasure(newMeasure.inputComponent(), (DefaultMeasure<?>) newMeasure);
   }
 
+  /**
+   * Thread safe
+   */
   private void logOnce(String metricKey, String msg, Object... params) {
-    if (!alreadyLogged.contains(metricKey)) {
+    if (alreadyLogged.add(metricKey)) {
       LOG.warn(msg, params);
-      alreadyLogged.add(metricKey);
     }
   }
 
@@ -313,11 +315,11 @@ public class DefaultSensorStorage implements SensorStorage {
     }
   }
 
-  public boolean isDeprecatedMetric(String metricKey) {
+  public static boolean isDeprecatedMetric(String metricKey) {
     return DEPRECATED_METRICS_KEYS.contains(metricKey);
   }
 
-  public boolean isPlatformMetric(String metricKey) {
+  public static boolean isPlatformMetric(String metricKey) {
     return PLATFORM_METRICS_KEYS.contains(metricKey);
   }
 
@@ -325,7 +327,7 @@ public class DefaultSensorStorage implements SensorStorage {
     return this.byLineMetrics.contains(metric);
   }
 
-  public void validateCoverageMeasure(String value, InputFile inputFile) {
+  public static void validateCoverageMeasure(String value, InputFile inputFile) {
     Map<Integer, Integer> m = KeyValueFormat.parseIntInt(value);
     validatePositiveLine(m, inputFile.absolutePath());
     validateMaxLine(m, inputFile);
@@ -349,6 +351,9 @@ public class DefaultSensorStorage implements SensorStorage {
     }
   }
 
+  /**
+   * Thread safe assuming that each issues for each file are only written once.
+   */
   @Override
   public void store(Issue issue) {
     if (issue.primaryLocation().inputComponent() instanceof DefaultInputFile) {
index 490f067d7644ace4ffbf6fc7d9d395297fcbb080..5af99184414b6894f829c610c3f07529da98f029 100644 (file)
  */
 package org.sonar.scanner.sensor.coverage;
 
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableList.Builder;
 import java.util.Collection;
 import java.util.Iterator;
+
+import javax.annotation.concurrent.Immutable;
+
 import org.picocontainer.Startable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -32,20 +32,26 @@ import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.utils.WildcardPattern;
 
-public class CoverageExclusions implements Startable {
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
 
+@Immutable
+public class CoverageExclusions implements Startable {
   private static final Logger LOG = LoggerFactory.getLogger(CoverageExclusions.class);
 
-  private final Configuration settings;
   private Collection<WildcardPattern> exclusionPatterns;
 
   public CoverageExclusions(Configuration settings) {
-    this.settings = settings;
+    Builder<WildcardPattern> builder = ImmutableList.builder();
+    for (String pattern : settings.getStringArray(CoreProperties.PROJECT_COVERAGE_EXCLUSIONS_PROPERTY)) {
+      builder.add(WildcardPattern.create(pattern));
+    }
+    exclusionPatterns = builder.build();
   }
 
   @Override
   public void start() {
-    initPatterns();
+    log("Excluded sources for coverage: ", exclusionPatterns);
   }
 
   @Override
@@ -62,16 +68,6 @@ public class CoverageExclusions implements Startable {
     return found;
   }
 
-  @VisibleForTesting
-  final void initPatterns() {
-    Builder<WildcardPattern> builder = ImmutableList.builder();
-    for (String pattern : settings.getStringArray(CoreProperties.PROJECT_COVERAGE_EXCLUSIONS_PROPERTY)) {
-      builder.add(WildcardPattern.create(pattern));
-    }
-    exclusionPatterns = builder.build();
-    log("Excluded sources for coverage: ", exclusionPatterns);
-  }
-
   private static void log(String title, Collection<WildcardPattern> patterns) {
     if (!patterns.isEmpty()) {
       LOG.info(title);
index 8e2cb47094e4be28ae406f3ee53241cb1e8e7084..a5128b5baf3de6e00d13a5e2dd62fcfd577d71db 100644 (file)
  */
 package org.sonar.scanner.storage;
 
-import com.google.common.collect.Sets;
 import com.persistit.Exchange;
 import com.persistit.Key;
 import com.persistit.KeyFilter;
 import com.persistit.exception.PersistitException;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.NoSuchElementException;
 import java.util.Set;
 import javax.annotation.CheckForNull;
@@ -242,7 +242,7 @@ public class Storage<V> {
   @SuppressWarnings("rawtypes")
   public Set keySet(Object key) {
     try {
-      Set<Object> keys = Sets.newLinkedHashSet();
+      Set<Object> keys = new LinkedHashSet<>();
       exchange.clear();
       Exchange iteratorExchange = new Exchange(exchange);
       iteratorExchange.append(key);
@@ -259,7 +259,7 @@ public class Storage<V> {
   @SuppressWarnings("rawtypes")
   public Set keySet(Object firstKey, Object secondKey) {
     try {
-      Set<Object> keys = Sets.newLinkedHashSet();
+      Set<Object> keys = new LinkedHashSet<>();
       exchange.clear();
       Exchange iteratorExchange = new Exchange(exchange);
       iteratorExchange.append(firstKey);
@@ -281,7 +281,7 @@ public class Storage<V> {
    */
   public Set<Object> keySet() {
     try {
-      Set<Object> keys = Sets.newLinkedHashSet();
+      Set<Object> keys = new LinkedHashSet<>();
       exchange.clear();
       Exchange iteratorExchange = new Exchange(exchange);
       iteratorExchange.append(Key.BEFORE);
index 60a8f7140bba60d12b8f5ed2663ec898b973d8cd..ce458c8bb3a13fb5d431dbda684a58eb47e6f750 100644 (file)
  */
 package org.sonar.scanner.analysis;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import java.io.File;
 import java.io.IOException;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
-import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.api.utils.TempFolder;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 public class AnalysisTempFolderProviderTest {
 
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
 
   private AnalysisTempFolderProvider tempFolderProvider;
-  private ProjectReactor projectReactor;
+  private InputModuleHierarchy moduleHierarchy;
 
   @Before
   public void setUp() {
     tempFolderProvider = new AnalysisTempFolderProvider();
-    projectReactor = mock(ProjectReactor.class);
-    ProjectDefinition projectDefinition = mock(ProjectDefinition.class);
-    when(projectReactor.getRoot()).thenReturn(projectDefinition);
-    when(projectDefinition.getWorkDir()).thenReturn(temp.getRoot());
+    moduleHierarchy = mock(InputModuleHierarchy.class);
+    DefaultInputModule module = mock(DefaultInputModule.class);
+    when(moduleHierarchy.root()).thenReturn(module);
+    when(module.getWorkDir()).thenReturn(temp.getRoot());
   }
 
   @Test
   public void createTempFolder() throws IOException {
     File defaultDir = new File(temp.getRoot(), AnalysisTempFolderProvider.TMP_NAME);
 
-    TempFolder tempFolder = tempFolderProvider.provide(projectReactor);
+    TempFolder tempFolder = tempFolderProvider.provide(moduleHierarchy);
     tempFolder.newDir();
     tempFolder.newFile();
     assertThat(defaultDir).exists();
index e46702cb90e8ea644cf569c676a60b8e343a5e86..33717366d0715b2c5aed485d5f8f1f8fce13fcb7 100644 (file)
  */
 package org.sonar.scanner.cpd;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -32,8 +37,8 @@ import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
-import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.scan.filesystem.PathResolver;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
@@ -50,10 +55,6 @@ import org.sonar.scanner.protocol.output.ScannerReportWriter;
 import org.sonar.scanner.report.ReportPublisher;
 import org.sonar.scanner.scan.filesystem.InputComponentStore;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 public class CpdExecutorTest {
   @Rule
   public LogTester logTester = new LogTester();
@@ -65,7 +66,7 @@ public class CpdExecutorTest {
   public ExpectedException thrown = ExpectedException.none();
 
   private CpdExecutor executor;
-  private MapSettings settings;
+  private CpdSettings settings;
   private SonarCpdBlockIndex index;
   private ReportPublisher publisher;
   private ScannerReportReader reader;
@@ -80,15 +81,15 @@ public class CpdExecutorTest {
     File outputDir = temp.newFolder();
     baseDir = temp.newFolder();
 
-    settings = new MapSettings();
+    settings = mock(CpdSettings.class);
     publisher = mock(ReportPublisher.class);
     when(publisher.getWriter()).thenReturn(new ScannerReportWriter(outputDir));
-    index = new SonarCpdBlockIndex(publisher, settings.asConfig());
-    componentStore = new InputComponentStore(new PathResolver());
-    executor = new CpdExecutor(settings.asConfig(), index, publisher, componentStore);
-    reader = new ScannerReportReader(outputDir);
 
-    componentStore.put(TestInputFileBuilder.newDefaultInputModule("foo", baseDir));
+    index = new SonarCpdBlockIndex(publisher, settings);
+    DefaultInputModule inputModule = TestInputFileBuilder.newDefaultInputModule("foo", baseDir);
+    componentStore = new InputComponentStore(new PathResolver(), inputModule);
+    executor = new CpdExecutor(settings, index, publisher, componentStore);
+    reader = new ScannerReportReader(outputDir);
 
     batchComponent1 = createComponent("src/Foo.php", 5);
     batchComponent2 = createComponent("src/Foo2.php", 5);
@@ -104,22 +105,6 @@ public class CpdExecutorTest {
     return file;
   }
 
-  @Test
-  public void defaultMinimumTokens() {
-    assertThat(executor.getMinimumTokens("java")).isEqualTo(100);
-  }
-
-  @Test
-  public void minimumTokensByLanguage() {
-    settings.setProperty("sonar.cpd.java.minimumTokens", "42");
-    settings.setProperty("sonar.cpd.php.minimumTokens", "33");
-    assertThat(executor.getMinimumTokens("java")).isEqualTo(42);
-
-    settings.setProperty("sonar.cpd.java.minimumTokens", "42");
-    settings.setProperty("sonar.cpd.php.minimumTokens", "33");
-    assertThat(executor.getMinimumTokens("php")).isEqualTo(33);
-  }
-
   @Test
   public void testNothingToSave() {
     executor.saveDuplications(batchComponent1, Collections.<CloneGroup>emptyList());
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/cpd/CpdSettingsTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/cpd/CpdSettingsTest.java
new file mode 100644 (file)
index 0000000..c7774c7
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.scanner.cpd;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Optional;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
+import org.sonar.api.config.Configuration;
+
+public class CpdSettingsTest {
+  private CpdSettings cpdSettings;
+  private Configuration configuration;
+  private DefaultInputModule module;
+
+  @Before
+  public void setUp() {
+    module = mock(DefaultInputModule.class);
+    InputModuleHierarchy hierarchy = mock(InputModuleHierarchy.class);
+    when(hierarchy.root()).thenReturn(module);
+    configuration = mock(Configuration.class);
+    cpdSettings = new CpdSettings(configuration, hierarchy);
+  }
+
+  @Test
+  public void defaultMinimumTokens() {
+    when(configuration.getInt(anyString())).thenReturn(Optional.empty());
+    assertThat(cpdSettings.getMinimumTokens("java")).isEqualTo(100);
+  }
+
+  @Test
+  public void minimumTokensByLanguage() {
+    when(configuration.getInt("sonar.cpd.java.minimumTokens")).thenReturn(Optional.of(42));
+    when(configuration.getInt("sonar.cpd.php.minimumTokens")).thenReturn(Optional.of(33));
+
+    assertThat(cpdSettings.getMinimumTokens("java")).isEqualTo(42);
+    assertThat(cpdSettings.getMinimumTokens("php")).isEqualTo(33);
+  }
+}
index d82bb5d20b113b1d5510e64439f52c4baef57eb8..d7d16341d8fe3ec8c497e0db4d66d28382d832a9 100644 (file)
  */
 package org.sonar.scanner.index;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import java.io.IOException;
 import java.util.Collections;
 import org.junit.Before;
@@ -44,10 +48,6 @@ import org.sonar.scanner.scan.filesystem.InputComponentStore;
 import org.sonar.scanner.scan.measure.MeasureCache;
 import org.sonar.scanner.sensor.DefaultSensorStorage;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 public class DefaultIndexTest {
 
   @org.junit.Rule
@@ -83,10 +83,10 @@ public class DefaultIndexTest {
     rootDef.addSubProject(moduleBDef);
     moduleBDef.addSubProject(moduleB1Def);
 
-    project = new Project(rootDef);
-    moduleA = new Project(moduleADef);
-    moduleB = new Project(moduleBDef);
-    moduleB1 = new Project(moduleB1Def);
+    project = new Project(new DefaultInputModule(rootDef));
+    moduleA = new Project(new DefaultInputModule(moduleADef));
+    moduleB = new Project(new DefaultInputModule(moduleBDef));
+    moduleB1 = new Project(new DefaultInputModule(moduleB1Def));
 
     RulesProfile rulesProfile = RulesProfile.create();
     rule = Rule.create("repoKey", "ruleKey", "Rule");
index a0aae458ed906910b280b7f1db23fc988c11f7d7..718f16ceb62eeee8a2c57ef09195ee2e63b5eb01 100644 (file)
@@ -53,12 +53,11 @@ public class EnforceIssuesFilterTest {
     issue = mock(FilterableIssue.class);
     chain = mock(IssueFilterChain.class);
     when(chain.accept(issue)).thenReturn(true);
-
-    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, inputComponentStore);
   }
 
   @Test
   public void shouldPassToChainIfNoConfiguredPatterns() {
+    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, inputComponentStore);
     assertThat(ignoreFilter.accept(issue, chain)).isTrue();
     verify(chain).accept(issue);
   }
@@ -76,6 +75,7 @@ public class EnforceIssuesFilterTest {
     when(rulePattern.match(rule)).thenReturn(false);
     when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(ImmutableList.of(matching));
 
+    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, inputComponentStore);
     assertThat(ignoreFilter.accept(issue, chain)).isTrue();
     verify(chain).accept(issue);
   }
@@ -100,6 +100,7 @@ public class EnforceIssuesFilterTest {
     when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(ImmutableList.of(matching));
     when(inputComponentStore.getByKey(componentKey)).thenReturn(createComponentWithPath(path));
 
+    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, inputComponentStore);
     assertThat(ignoreFilter.accept(issue, chain)).isTrue();
     verifyZeroInteractions(chain);
   }
@@ -128,6 +129,7 @@ public class EnforceIssuesFilterTest {
     when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(ImmutableList.of(matching));
     when(inputComponentStore.getByKey(componentKey)).thenReturn(createComponentWithPath(path));
 
+    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, inputComponentStore);
     assertThat(ignoreFilter.accept(issue, chain)).isFalse();
     verifyZeroInteractions(chain);
   }
@@ -152,6 +154,7 @@ public class EnforceIssuesFilterTest {
     when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(ImmutableList.of(matching));
     when(inputComponentStore.getByKey(componentKey)).thenReturn(null);
 
+    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, inputComponentStore);
     assertThat(ignoreFilter.accept(issue, chain)).isFalse();
     verifyZeroInteractions(chain);
   }
index 0cf540f0b7af27b8193c6449008b9d8fea57ca65..dde76bf67229ea47a25618ad14a123d930eae10e 100644 (file)
  */
 package org.sonar.scanner.issue.tracking;
 
+import static org.apache.commons.codec.digest.DigestUtils.md5Hex;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import java.io.File;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.util.Collections;
+
 import org.apache.commons.io.FileUtils;
 import org.junit.Before;
 import org.junit.Rule;
@@ -34,12 +42,6 @@ import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.batch.fs.internal.DefaultInputModule;
 
-import static org.apache.commons.codec.digest.DigestUtils.md5Hex;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 public class SourceHashHolderTest {
 
   @Rule
@@ -51,10 +53,11 @@ public class SourceHashHolderTest {
   DefaultInputFile file;
 
   private File ioFile;
-  private ProjectDefinition def = ProjectDefinition.create();
+  private ProjectDefinition def;
 
   @Before
   public void setUp() throws Exception {
+    def = mock(ProjectDefinition.class);
     lastSnapshots = mock(ServerLineHashesLoader.class);
     file = mock(DefaultInputFile.class);
     ioFile = temp.newFile();
@@ -83,7 +86,7 @@ public class SourceHashHolderTest {
   public void should_lazy_load_reference_hashes_when_status_changed() throws Exception {
     final String source = "source";
     FileUtils.write(ioFile, source, StandardCharsets.UTF_8);
-    def.setKey("foo");
+    when(def.getKeyWithBranch()).thenReturn("foo");
     when(file.relativePath()).thenReturn("src/Foo.java");
     String key = "foo:src/Foo.java";
     when(file.status()).thenReturn(InputFile.Status.CHANGED);
@@ -100,8 +103,8 @@ public class SourceHashHolderTest {
   public void should_lazy_load_reference_hashes_when_status_changed_on_branch() throws Exception {
     final String source = "source";
     FileUtils.write(ioFile, source, StandardCharsets.UTF_8);
-    def.setKey("foo");
-    def.properties().put(CoreProperties.PROJECT_BRANCH_PROPERTY, "myBranch");
+    when(def.getKeyWithBranch()).thenReturn("foo:myBranch");
+    when(def.properties()).thenReturn(Collections.singletonMap(CoreProperties.PROJECT_BRANCH_PROPERTY, "myBranch"));
     when(file.relativePath()).thenReturn("src/Foo.java");
     String key = "foo:myBranch:src/Foo.java";
     when(file.status()).thenReturn(InputFile.Status.CHANGED);
index b2d4722cf1c01852d4171a3a5aa942443ce7028d..55a3234a5224399414f38ded4bca0c4796f7bc39 100644 (file)
@@ -670,6 +670,7 @@ public class FileSystemMediumTest {
       .newScanTask(new File(projectDir, "sonar-project.properties"))
       .start();
 
+    System.out.println(logs.getAsString());
     assertThat(result.inputFiles()).hasSize(4);
     assertThat(result.inputDirs()).hasSize(4);
   }
index 5afcb9ee1f5a16628443f407180a7008078f7bbc..dcea19af2854b383f49916de685e4d071dc19cdf 100644 (file)
  */
 package org.sonar.scanner.phases;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import java.io.IOException;
 import java.util.Collections;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import org.sonar.api.batch.Sensor;
 import org.sonar.api.batch.SensorContext;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.batch.fs.internal.DefaultInputModule;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
 import org.sonar.api.resources.Project;
 import org.sonar.scanner.bootstrap.ScannerExtensionDictionnary;
 import org.sonar.scanner.events.EventBus;
 import org.sonar.scanner.sensor.SensorStrategy;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 public class SensorsExecutorTest {
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
@@ -83,12 +86,17 @@ public class SensorsExecutorTest {
     when(selector.selectSensors(any(DefaultInputModule.class), eq(false))).thenReturn(Collections.singleton(perModuleSensor));
     when(selector.selectSensors(any(DefaultInputModule.class), eq(true))).thenReturn(Collections.singleton(globalSensor));
 
-    DefaultInputModule rootModule = TestInputFileBuilder.newDefaultInputModule("root", temp.newFolder());
-    rootModuleExecutor = new SensorsExecutor(selector, rootModule, mock(EventBus.class), strategy);
+    ProjectDefinition childDef = ProjectDefinition.create().setKey("sub").setBaseDir(temp.newFolder());
+    ProjectDefinition rootDef = ProjectDefinition.create().setKey("root").setBaseDir(temp.newFolder());
+
+    DefaultInputModule rootModule = TestInputFileBuilder.newDefaultInputModule(rootDef);
+    DefaultInputModule subModule = TestInputFileBuilder.newDefaultInputModule(childDef);
+
+    InputModuleHierarchy hierarchy = mock(InputModuleHierarchy.class);
+    when(hierarchy.isRoot(rootModule)).thenReturn(true);
 
-    DefaultInputModule subModule = TestInputFileBuilder.newDefaultInputModule("sub", temp.newFolder());
-    rootModule.definition().addSubProject(subModule.definition());
-    subModuleExecutor = new SensorsExecutor(selector, subModule, mock(EventBus.class), strategy);
+    rootModuleExecutor = new SensorsExecutor(selector, rootModule, hierarchy, mock(EventBus.class), strategy);
+    subModuleExecutor = new SensorsExecutor(selector, subModule, hierarchy, mock(EventBus.class), strategy);
   }
 
   @Test
index 47ceca948880a80cd1eeb794e7b0f310bed92bd9..9f19852e6fd900554b2d60eaadd4e829e89179d0 100644 (file)
@@ -26,6 +26,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import org.sonar.api.batch.AnalysisMode;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
 import org.sonar.api.batch.postjob.issue.PostJobIssue;
 import org.sonar.api.batch.rule.Severity;
@@ -51,9 +52,10 @@ public class DefaultPostJobContextTest {
   private AnalysisMode analysisMode;
 
   @Before
-  public void prepare() {
+  public void setUp() throws IOException {
     issueCache = mock(IssueCache.class);
-    componentStore = new InputComponentStore(new PathResolver());
+    DefaultInputModule rootModule = TestInputFileBuilder.newDefaultInputModule("foo", temp.newFolder());
+    componentStore = new InputComponentStore(new PathResolver(), rootModule);
     settings = new MapSettings();
     analysisMode = mock(AnalysisMode.class);
     context = new DefaultPostJobContext(settings.asConfig(), settings, issueCache, componentStore, analysisMode);
@@ -85,8 +87,6 @@ public class DefaultPostJobContextTest {
     assertThat(issue.inputComponent()).isNull();
 
     String moduleKey = "foo";
-    componentStore.put(TestInputFileBuilder.newDefaultInputModule("foo", temp.newFolder()));
-
     componentStore.put(new TestInputFileBuilder(moduleKey, "src/Foo.php").build());
     assertThat(issue.inputComponent()).isNotNull();
 
index a3d55dfa855518e4a34a47ffc586bbc113a36e67..b2853116007c750121229fd9761f0380a69f0242 100644 (file)
@@ -42,6 +42,7 @@ import org.sonar.api.batch.events.SensorExecutionHandler;
 import org.sonar.api.batch.events.SensorExecutionHandler.SensorExecutionEvent;
 import org.sonar.api.batch.events.SensorsPhaseHandler;
 import org.sonar.api.batch.events.SensorsPhaseHandler.SensorsPhaseEvent;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.resources.Project;
 import org.sonar.api.utils.System2;
 import org.sonar.scanner.bootstrap.GlobalProperties;
@@ -132,7 +133,7 @@ public class PhasesSumUpTimeProfilerTest {
   }
 
   private Project mockProject(String name, boolean isRoot) {
-    return new Project(ProjectDefinition.create().setName(name).setKey(name));
+    return new Project(new DefaultInputModule(ProjectDefinition.create().setName(name).setKey(name)));
   }
 
   private void fakeAnalysis(PhasesSumUpTimeProfiler profiler, final Project module) {
index 3c65a91b5988bddee1c1495e4d6d80ae63015b70..77fc6206b24730397da477f221c5f6e560a5a124 100644 (file)
@@ -31,6 +31,8 @@ import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import org.sonar.api.batch.AnalysisMode;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
@@ -65,6 +67,7 @@ public class AnalysisContextReportPublisherTest {
   private System2 system2;
   private ProjectRepositories projectRepos;
   private GlobalConfiguration globalSettings;
+  private InputModuleHierarchy hierarchy;
 
   @Before
   public void prepare() throws Exception {
@@ -73,7 +76,8 @@ public class AnalysisContextReportPublisherTest {
     when(system2.properties()).thenReturn(new Properties());
     projectRepos = mock(ProjectRepositories.class);
     globalSettings = mock(GlobalConfiguration.class);
-    publisher = new AnalysisContextReportPublisher(analysisMode, pluginRepo, system2, projectRepos, globalSettings);
+    hierarchy = mock(InputModuleHierarchy.class);
+    publisher = new AnalysisContextReportPublisher(analysisMode, pluginRepo, system2, projectRepos, globalSettings, hierarchy);
   }
 
   @Test
@@ -95,7 +99,7 @@ public class AnalysisContextReportPublisherTest {
 
     ScannerReportWriter writer = new ScannerReportWriter(temp.newFolder());
     publisher.init(writer);
-    publisher.dumpModuleSettings(ProjectDefinition.create().setProperty("sonar.projectKey", "foo"));
+    publisher.dumpModuleSettings(new DefaultInputModule("foo"));
 
     assertThat(writer.getFileStructure().analysisLog()).doesNotExist();
   }
@@ -122,8 +126,7 @@ public class AnalysisContextReportPublisherTest {
     when(projectRepos.moduleExists("foo")).thenReturn(true);
     when(projectRepos.settings("foo")).thenReturn(ImmutableMap.of(COM_FOO, "bar", SONAR_SKIP, "true"));
 
-    publisher.dumpModuleSettings(ProjectDefinition.create()
-      .setProperty("sonar.projectKey", "foo"));
+    publisher.dumpModuleSettings(new DefaultInputModule("foo"));
 
     String content = FileUtils.readFileToString(writer.getFileStructure().analysisLog());
     assertThat(content).doesNotContain(COM_FOO);
@@ -144,10 +147,10 @@ public class AnalysisContextReportPublisherTest {
     assertThat(content).containsOnlyOnce(COM_FOO);
     assertThat(content).doesNotContain(SONAR_SKIP);
 
-    publisher.dumpModuleSettings(ProjectDefinition.create()
+    publisher.dumpModuleSettings(new DefaultInputModule(ProjectDefinition.create()
       .setProperty("sonar.projectKey", "foo")
       .setProperty(COM_FOO, "bar")
-      .setProperty(SONAR_SKIP, "true"));
+      .setProperty(SONAR_SKIP, "true")));
 
     content = FileUtils.readFileToString(writer.getFileStructure().analysisLog());
     assertThat(content).containsOnlyOnce(COM_FOO);
@@ -170,9 +173,9 @@ public class AnalysisContextReportPublisherTest {
     assertThat(content).containsOnlyOnce(BIZ);
     assertThat(content).containsSequence(BIZ, FOO);
 
-    publisher.dumpModuleSettings(ProjectDefinition.create()
+    publisher.dumpModuleSettings(new DefaultInputModule(ProjectDefinition.create()
       .setProperty("sonar.projectKey", "foo")
-      .setProperty("env." + FOO, "BAR"));
+      .setProperty("env." + FOO, "BAR")));
 
     content = FileUtils.readFileToString(writer.getFileStructure().analysisLog());
     assertThat(content).containsOnlyOnce(FOO);
@@ -187,12 +190,12 @@ public class AnalysisContextReportPublisherTest {
 
     assertThat(writer.getFileStructure().analysisLog()).exists();
 
-    publisher.dumpModuleSettings(ProjectDefinition.create()
+    publisher.dumpModuleSettings(new DefaultInputModule(ProjectDefinition.create()
       .setProperty("sonar.projectKey", "foo")
       .setProperty("sonar.projectKey", "foo")
       .setProperty("sonar.login", "my_token")
       .setProperty("sonar.password", "azerty")
-      .setProperty("sonar.cpp.license.secured", "AZERTY"));
+      .setProperty("sonar.cpp.license.secured", "AZERTY")));
 
     assertThat(FileUtils.readFileToString(writer.getFileStructure().analysisLog())).containsSequence(
       "sonar.cpp.license.secured=******",
@@ -222,14 +225,15 @@ public class AnalysisContextReportPublisherTest {
     ScannerReportWriter writer = new ScannerReportWriter(temp.newFolder());
     publisher.init(writer);
 
-    ProjectDefinition module = ProjectDefinition.create()
+    DefaultInputModule module = new DefaultInputModule(ProjectDefinition.create()
       .setProperty("sonar.projectKey", "foo")
-      .setProperty(SONAR_SKIP, "true");
+      .setProperty(SONAR_SKIP, "true"));
 
-    ProjectDefinition.create()
+    DefaultInputModule parent = new DefaultInputModule(ProjectDefinition.create()
       .setProperty("sonar.projectKey", "parent")
-      .setProperty(SONAR_SKIP, "true")
-      .addSubProject(module);
+      .setProperty(SONAR_SKIP, "true"));
+
+    when(hierarchy.parent(module)).thenReturn(parent);
 
     publisher.dumpModuleSettings(module);
 
index 5551ca8f67902657191e0eefa5550e5dca4b3414..395d227be622e7d386099f0ea39635a7f233ade6 100644 (file)
@@ -26,6 +26,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
 import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
 import org.sonar.api.measures.CoreMetrics;
@@ -56,8 +57,8 @@ public class CoveragePublisherTest {
   public void prepare() throws IOException {
     String moduleKey = "foo";
     inputFile = new TestInputFileBuilder(moduleKey, "src/Foo.php").setLines(5).build();
-    InputComponentStore componentCache = new InputComponentStore(new PathResolver());
-    componentCache.put(TestInputFileBuilder.newDefaultInputModule(moduleKey, temp.newFolder()));
+    DefaultInputModule rootModule = TestInputFileBuilder.newDefaultInputModule(moduleKey, temp.newFolder());
+    InputComponentStore componentCache = new InputComponentStore(new PathResolver(), rootModule);
     componentCache.put(inputFile);
 
     measureCache = mock(MeasureCache.class);
index d3dfe4cc3e9274ba098a4b7db33e8a9232c39d5a..6f513738f458d48813d86e96409647c412ce02fd 100644 (file)
@@ -69,8 +69,7 @@ public class MeasuresPublisherTest {
     String moduleKey = "foo";
     inputModule = TestInputFileBuilder.newDefaultInputModule(moduleKey, temp.newFolder());
     inputFile = new TestInputFileBuilder(moduleKey, "src/Foo.php").setPublish(true).build();
-    InputComponentStore componentCache = new InputComponentStore(new PathResolver());
-    componentCache.put(inputModule);
+    InputComponentStore componentCache = new InputComponentStore(new PathResolver(), inputModule);
     componentCache.put(inputFile);
     measureCache = mock(MeasureCache.class);
     when(measureCache.byComponentKey(anyString())).thenReturn(Collections.<DefaultMeasure<?>>emptyList());
index e59cbd47ee48ec2ae3bf5e4106a1e706cd76b92b..3d3dddfe821e137aab0d7ec1170ac7b7b2296833 100644 (file)
  */
 package org.sonar.scanner.report;
 
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import java.io.File;
 import java.util.Date;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -32,53 +39,48 @@ import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
 import org.sonar.api.config.internal.MapSettings;
 import org.sonar.scanner.ProjectAnalysisInfo;
+import org.sonar.scanner.cpd.CpdSettings;
 import org.sonar.scanner.protocol.output.ScannerReport;
 import org.sonar.scanner.protocol.output.ScannerReportReader;
 import org.sonar.scanner.protocol.output.ScannerReportWriter;
 import org.sonar.scanner.rule.ModuleQProfiles;
 import org.sonar.scanner.rule.QProfile;
 
-import static java.util.Arrays.asList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 public class MetadataPublisherTest {
 
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
 
-  private ProjectDefinition projectDef;
   private DefaultInputModule rootModule;
   private MetadataPublisher underTest;
   private MapSettings settings;
   private ModuleQProfiles qProfiles;
   private ProjectAnalysisInfo projectAnalysisInfo;
+  private CpdSettings cpdSettings;
   private InputModuleHierarchy inputModuleHierarchy;
 
   @Before
   public void prepare() {
-    projectDef = ProjectDefinition.create().setKey("foo");
-    rootModule = new DefaultInputModule(projectDef, TestInputFileBuilder.nextBatchId());
     projectAnalysisInfo = mock(ProjectAnalysisInfo.class);
+    cpdSettings = mock(CpdSettings.class);
     when(projectAnalysisInfo.analysisDate()).thenReturn(new Date(1234567L));
-    inputModuleHierarchy = mock(InputModuleHierarchy.class);
-    when(inputModuleHierarchy.root()).thenReturn(rootModule);
     settings = new MapSettings();
     qProfiles = mock(ModuleQProfiles.class);
-    underTest = new MetadataPublisher(projectAnalysisInfo, inputModuleHierarchy, settings.asConfig(), qProfiles);
+    createPublisher(ProjectDefinition.create().setKey("foo"));
+  }
+
+  private void createPublisher(ProjectDefinition def) {
+    rootModule = new DefaultInputModule(def, TestInputFileBuilder.nextBatchId());
+    inputModuleHierarchy = mock(InputModuleHierarchy.class);
+    when(inputModuleHierarchy.root()).thenReturn(rootModule);
+    underTest = new MetadataPublisher(projectAnalysisInfo, inputModuleHierarchy, settings.asConfig(), qProfiles, cpdSettings);
   }
 
   @Test
   public void write_metadata() throws Exception {
     settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "true");
     Date date = new Date();
-    when(qProfiles.findAll()).thenReturn(asList(new QProfile()
-      .setKey("q1")
-      .setName("Q1")
-      .setLanguage("java")
-      .setRulesUpdatedAt(date)));
+    when(qProfiles.findAll()).thenReturn(asList(new QProfile("q1", "Q1", "java", date)));
     File outputDir = temp.newFolder();
     ScannerReportWriter writer = new ScannerReportWriter(outputDir);
 
@@ -89,7 +91,6 @@ public class MetadataPublisherTest {
     assertThat(metadata.getAnalysisDate()).isEqualTo(1234567L);
     assertThat(metadata.getProjectKey()).isEqualTo("foo");
     assertThat(metadata.getProjectKey()).isEqualTo("foo");
-    assertThat(metadata.getCrossProjectDuplicationActivated()).isTrue();
     assertThat(metadata.getQprofilesPerLanguage()).containsOnly(entry("java", org.sonar.scanner.protocol.output.ScannerReport.Metadata.QProfile.newBuilder()
       .setKey("q1")
       .setName("Q1")
@@ -100,10 +101,14 @@ public class MetadataPublisherTest {
 
   @Test
   public void write_project_branch() throws Exception {
+    when(cpdSettings.isCrossProjectDuplicationEnabled()).thenReturn(false);
     settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "true");
     settings.setProperty(CoreProperties.PROJECT_BRANCH_PROPERTY, "myBranch");
-    projectDef.properties().put(CoreProperties.PROJECT_BRANCH_PROPERTY, "myBranch");
-    projectDef.setKey("foo");
+
+    ProjectDefinition projectDef = ProjectDefinition.create()
+      .setKey("foo")
+      .setProperty(CoreProperties.PROJECT_BRANCH_PROPERTY, "myBranch");
+    createPublisher(projectDef);
 
     File outputDir = temp.newFolder();
     ScannerReportWriter writer = new ScannerReportWriter(outputDir);
@@ -115,7 +120,6 @@ public class MetadataPublisherTest {
     assertThat(metadata.getAnalysisDate()).isEqualTo(1234567L);
     assertThat(metadata.getProjectKey()).isEqualTo("foo");
     assertThat(metadata.getBranch()).isEqualTo("myBranch");
-    // Cross project duplication disabled on branches
     assertThat(metadata.getCrossProjectDuplicationActivated()).isFalse();
   }
 
index 7b93925349ebe8691ed9a721d7c9a67ed562dfbc..635f15e74f515a7a59d6d7e99ee6ef59502d75ff 100644 (file)
  */
 package org.sonar.scanner.report;
 
+import static org.apache.commons.io.FileUtils.readFileToString;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.PipedInputStream;
 import java.io.PipedOutputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -34,6 +43,8 @@ import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.api.config.PropertyDefinitions;
 import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.platform.Server;
@@ -44,20 +55,11 @@ import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.core.config.CorePropertyDefinitions;
 import org.sonar.scanner.analysis.DefaultAnalysisMode;
 import org.sonar.scanner.bootstrap.ScannerWsClient;
-import org.sonar.scanner.scan.ImmutableProjectReactor;
 import org.sonarqube.ws.WsCe;
 import org.sonarqube.ws.client.HttpException;
 import org.sonarqube.ws.client.WsRequest;
 import org.sonarqube.ws.client.WsResponse;
 
-import static org.apache.commons.io.FileUtils.readFileToString;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 public class ReportPublisherTest {
 
   @Rule
@@ -73,22 +75,23 @@ public class ReportPublisherTest {
   MapSettings settings = new MapSettings(new PropertyDefinitions(CorePropertyDefinitions.all()));
   ScannerWsClient wsClient;
   Server server = mock(Server.class);
-  ImmutableProjectReactor reactor = mock(ImmutableProjectReactor.class);
-  ProjectDefinition root;
+  InputModuleHierarchy moduleHierarchy = mock(InputModuleHierarchy.class);
+  DefaultInputModule root;
   AnalysisContextReportPublisher contextPublisher = mock(AnalysisContextReportPublisher.class);
 
   @Before
   public void setUp() {
     wsClient = mock(ScannerWsClient.class, Mockito.RETURNS_DEEP_STUBS);
-    root = ProjectDefinition.create().setKey("struts").setWorkDir(temp.getRoot());
-    when(reactor.getRoot()).thenReturn(root);
+    root = new DefaultInputModule(ProjectDefinition.create().setKey("struts").setWorkDir(temp.getRoot()));
+    when(moduleHierarchy.root()).thenReturn(root);
     when(server.getPublicRootUrl()).thenReturn("https://localhost");
     when(server.getVersion()).thenReturn("6.4");
   }
 
   @Test
   public void log_and_dump_information_about_report_uploading() throws IOException {
-    ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]);
+    ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, moduleHierarchy, mode, mock(TempFolder.class),
+      new ReportPublisherStep[0]);
     settings.setProperty(CoreProperties.PROJECT_ORGANIZATION_PROPERTY, "MyOrg");
 
     underTest.logSuccess("TASK-123");
@@ -111,7 +114,8 @@ public class ReportPublisherTest {
 
   @Test
   public void parse_upload_error_message() throws IOException {
-    ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]);
+    ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, moduleHierarchy, mode, mock(TempFolder.class),
+      new ReportPublisherStep[0]);
     HttpException ex = new HttpException("url", 404, "{\"errors\":[{\"msg\":\"Organization with key 'MyOrg' does not exist\"}]}");
     WsResponse response = mock(WsResponse.class);
     when(response.failIfNotSuccessful()).thenThrow(ex);
@@ -125,7 +129,8 @@ public class ReportPublisherTest {
   @Test
   public void log_public_url_if_defined() throws IOException {
     when(server.getPublicRootUrl()).thenReturn("https://publicserver/sonarqube");
-    ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]);
+    ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, moduleHierarchy, mode, mock(TempFolder.class),
+      new ReportPublisherStep[0]);
 
     underTest.logSuccess("TASK-123");
 
@@ -146,7 +151,8 @@ public class ReportPublisherTest {
   @Test
   public void fail_if_public_url_malformed() throws IOException {
     when(server.getPublicRootUrl()).thenReturn("invalid");
-    ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]);
+    ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, moduleHierarchy, mode, mock(TempFolder.class),
+      new ReportPublisherStep[0]);
 
     exception.expect(MessageException.class);
     exception.expectMessage("Failed to parse public URL set in SonarQube server: invalid");
@@ -155,7 +161,8 @@ public class ReportPublisherTest {
 
   @Test
   public void log_but_not_dump_information_when_report_is_not_uploaded() {
-    ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]);
+    ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, moduleHierarchy, mode, mock(TempFolder.class),
+      new ReportPublisherStep[0]);
 
     underTest.logSuccess(/* report not uploaded, no server task */null);
 
@@ -172,7 +179,8 @@ public class ReportPublisherTest {
     settings.setProperty("sonar.batch.keepReport", true);
     Path reportDir = temp.getRoot().toPath().resolve("batch-report");
     Files.createDirectory(reportDir);
-    ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]);
+    ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, moduleHierarchy, mode, mock(TempFolder.class),
+      new ReportPublisherStep[0]);
 
     underTest.start();
     underTest.stop();
@@ -183,7 +191,7 @@ public class ReportPublisherTest {
   public void should_delete_report_by_default() throws IOException {
     Path reportDir = temp.getRoot().toPath().resolve("batch-report");
     Files.createDirectory(reportDir);
-    ReportPublisher job = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]);
+    ReportPublisher job = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, moduleHierarchy, mode, mock(TempFolder.class), new ReportPublisherStep[0]);
 
     job.start();
     job.stop();
@@ -192,7 +200,8 @@ public class ReportPublisherTest {
 
   @Test
   public void test_ws_parameters() throws Exception {
-    ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]);
+    ReportPublisher underTest = new ReportPublisher(settings.asConfig(), wsClient, server, contextPublisher, moduleHierarchy, mode, mock(TempFolder.class),
+      new ReportPublisherStep[0]);
 
     settings.setProperty(CoreProperties.PROJECT_ORGANIZATION_PROPERTY, "MyOrg");
 
index 3624dcabf6503a6ea9017b81ffa1fa12e873cc6f..76f0dc7aee67d9b24003e4f1c247b2522d4ba458 100644 (file)
@@ -28,6 +28,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
 import org.sonar.api.scan.filesystem.PathResolver;
 import org.sonar.scanner.protocol.output.ScannerReportWriter;
@@ -55,8 +56,8 @@ public class SourcePublisherTest {
       .setCharset(StandardCharsets.ISO_8859_1)
       .build();
 
-    InputComponentStore componentStore = new InputComponentStore(new PathResolver());
-    componentStore.put(TestInputFileBuilder.newDefaultInputModule(moduleKey, baseDir));
+    DefaultInputModule rootModule = TestInputFileBuilder.newDefaultInputModule(moduleKey, baseDir);
+    InputComponentStore componentStore = new InputComponentStore(new PathResolver(), rootModule);
     componentStore.put(inputFile);
 
     publisher = new SourcePublisher(componentStore);
index a21efc17c55bdca5fc402b54c64dee2628266e7e..e5fda6b88c5b587d9329cc05c2688dbc54100c5c 100644 (file)
@@ -26,18 +26,9 @@ import static org.assertj.core.api.Assertions.assertThat;
 public class QProfileTest {
   @Test
   public void testEquals() {
-    QProfile q1 = new QProfile();
-    QProfile q2 = new QProfile();
-    QProfile q3 = new QProfile();
-
-    q1.setKey("k1");
-    q1.setName("name1");
-
-    q2.setKey("k1");
-    q2.setName("name2");
-
-    q3.setKey("k3");
-    q3.setName("name3");
+    QProfile q1 = new QProfile("k1", "name1", null, null);
+    QProfile q2 = new QProfile("k1", "name2", null, null);
+    QProfile q3 = new QProfile("k3", "name3", null, null);
 
     assertThat(q1).isEqualTo(q2);
     assertThat(q1).isNotEqualTo(q3);
index f68303521a5d7a928f9f2d3756e6cd581d6f6d27..cd5b36636f4a5435bfe2baa2971ed35cbc9d8277 100644 (file)
@@ -50,9 +50,9 @@ public class QProfileVerifierTest {
   public void before() throws Exception {
     fs = new DefaultFileSystem(temp.newFolder().toPath());
     profiles = mock(ModuleQProfiles.class);
-    QProfile javaProfile = new QProfile().setKey("p1").setName("My Java profile").setLanguage("java");
+    QProfile javaProfile = new QProfile("p1", "My Java profile", "java", null);
     when(profiles.findByLanguage("java")).thenReturn(javaProfile);
-    QProfile cobolProfile = new QProfile().setKey("p2").setName("My Cobol profile").setLanguage("cobol");
+    QProfile cobolProfile = new QProfile("p2", "My Cobol profile", "cobol", null);
     when(profiles.findByLanguage("cobol")).thenReturn(cobolProfile);
   }
 
index 8dff3fadef3250dc787334f6b254df47baec2b1c..2df62b25272e8f28768f4d63a3245ea8823ec1e9 100644 (file)
@@ -39,7 +39,7 @@ public class RulesProfileProviderTest {
 
   @Test
   public void merge_profiles() {
-    QProfile qProfile = new QProfile().setKey("java-sw").setName("Sonar way").setLanguage("java");
+    QProfile qProfile = new QProfile("java-sw", "Sonar way", "java", null);
     when(qProfiles.findAll()).thenReturn(Arrays.asList(qProfile));
 
     RulesProfile profile = provider.provide(qProfiles, new ActiveRulesBuilder().build(), settings.asConfig());
@@ -61,7 +61,7 @@ public class RulesProfileProviderTest {
   public void keep_compatibility_with_single_language_projects() {
     settings.setProperty("sonar.language", "java");
 
-    QProfile qProfile = new QProfile().setKey("java-sw").setName("Sonar way").setLanguage("java");
+    QProfile qProfile = new QProfile("java-sw", "Sonar way", "java", null);
     when(qProfiles.findByLanguage("java")).thenReturn(qProfile);
 
     RulesProfile profile = provider.provide(qProfiles, new ActiveRulesBuilder().build(), settings.asConfig());
@@ -74,7 +74,7 @@ public class RulesProfileProviderTest {
 
   @Test
   public void support_rule_templates() {
-    QProfile qProfile = new QProfile().setKey("java-sw").setName("Sonar way").setLanguage("java");
+    QProfile qProfile = new QProfile("java-sw", "Sonar way", "java", null);
     when(qProfiles.findAll()).thenReturn(Arrays.asList(qProfile));
     ActiveRulesBuilder activeRulesBuilder = new ActiveRulesBuilder();
     activeRulesBuilder.create(RuleKey.of("java", "S001")).setTemplateRuleKey("T001").setLanguage("java").activate();
index be0f77730389a939abad38bc378f46408fe5eadd..977fdc6a67c9af4144ae0186150617bf4e459f6c 100644 (file)
  */
 package org.sonar.scanner.scan;
 
-import org.junit.Before;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.HashMap;
+import java.util.Map;
+
 import org.junit.Test;
 import org.sonar.api.batch.fs.internal.DefaultInputModule;
 
-import static org.assertj.core.api.Assertions.assertThat;
-
 public class DefaultInputModuleHierarchyTest {
   private DefaultInputModuleHierarchy moduleHierarchy;
 
-  @Before
-  public void setUp() {
-    moduleHierarchy = new DefaultInputModuleHierarchy();
-  }
-
   @Test
   public void test() {
     DefaultInputModule root = new DefaultInputModule("root");
@@ -41,11 +38,14 @@ public class DefaultInputModuleHierarchyTest {
     DefaultInputModule mod3 = new DefaultInputModule("mod3");
     DefaultInputModule mod4 = new DefaultInputModule("mod4");
 
-    moduleHierarchy.setRoot(root);
-    moduleHierarchy.index(mod1, root);
-    moduleHierarchy.index(mod2, mod1);
-    moduleHierarchy.index(mod3, root);
-    moduleHierarchy.index(mod4, root);
+    Map<DefaultInputModule, DefaultInputModule> parents = new HashMap<>();
+
+    parents.put(mod1, root);
+    parents.put(mod2, mod1);
+    parents.put(mod3, root);
+    parents.put(mod4, root);
+
+    moduleHierarchy = new DefaultInputModuleHierarchy(parents);
 
     assertThat(moduleHierarchy.children(root)).containsOnly(mod1, mod3, mod4);
     assertThat(moduleHierarchy.children(mod4)).isEmpty();
@@ -58,4 +58,28 @@ public class DefaultInputModuleHierarchyTest {
 
     assertThat(moduleHierarchy.root()).isEqualTo(root);
   }
+
+  @Test
+  public void testOnlyRoot() {
+    DefaultInputModule root = new DefaultInputModule("root");
+    moduleHierarchy = new DefaultInputModuleHierarchy(root);
+
+    assertThat(moduleHierarchy.children(root)).isEmpty();
+    assertThat(moduleHierarchy.parent(root)).isNull();
+    assertThat(moduleHierarchy.root()).isEqualTo(root);
+  }
+
+  @Test
+  public void testParentChild() {
+    DefaultInputModule root = new DefaultInputModule("root");
+    DefaultInputModule mod1 = new DefaultInputModule("mod1");
+    moduleHierarchy = new DefaultInputModuleHierarchy(root, mod1);
+
+    assertThat(moduleHierarchy.children(root)).containsOnly(mod1);
+    assertThat(moduleHierarchy.children(mod1)).isEmpty();
+
+    assertThat(moduleHierarchy.parent(mod1)).isEqualTo(root);
+    assertThat(moduleHierarchy.parent(root)).isNull();
+    assertThat(moduleHierarchy.root()).isEqualTo(root);
+  }
 }
index 74faa55f153b85114a6430a1fc8173ed27a333fb..63bd38a6ada3569b522b9dac1d102984d8a978cf 100644 (file)
  */
 package org.sonar.scanner.scan;
 
-import org.junit.Before;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+
 import org.junit.Test;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.batch.fs.InputModule;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.scan.filesystem.PathResolver;
-import org.sonar.scanner.scan.filesystem.BatchIdGenerator;
 import org.sonar.scanner.scan.filesystem.InputComponentStore;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 public class ModuleIndexerTest {
   private ModuleIndexer indexer;
   private DefaultComponentTree tree;
   private DefaultInputModuleHierarchy moduleHierarchy;
-  private ImmutableProjectReactor reactor;
   private InputComponentStore componentStore;
 
-  @Before
-  public void setUp() {
-    reactor = mock(ImmutableProjectReactor.class);
-    componentStore = new InputComponentStore(new PathResolver());
+  public void createIndexer(DefaultInputModule rootModule) {
+    componentStore = new InputComponentStore(new PathResolver(), rootModule);
     tree = new DefaultComponentTree();
-    moduleHierarchy = new DefaultInputModuleHierarchy();
-    indexer = new ModuleIndexer(reactor, tree, componentStore, new BatchIdGenerator(), moduleHierarchy);
+    moduleHierarchy = mock(DefaultInputModuleHierarchy.class);
+    indexer = new ModuleIndexer(tree, componentStore, moduleHierarchy);
   }
-
+  
   @Test
   public void testIndex() {
-    ProjectDefinition root = ProjectDefinition.create().setKey("root");
-    ProjectDefinition mod1 = ProjectDefinition.create().setKey("mod1");
-    ProjectDefinition mod2 = ProjectDefinition.create().setKey("mod2");
-    ProjectDefinition mod3 = ProjectDefinition.create().setKey("mod3");
-    ProjectDefinition mod4 = ProjectDefinition.create().setKey("mod4");
+    ProjectDefinition rootDef = mock(ProjectDefinition.class);
+    ProjectDefinition def = mock(ProjectDefinition.class);
+    when(rootDef.getParent()).thenReturn(null);
+    when(def.getParent()).thenReturn(rootDef);
+    
+    DefaultInputModule root = mock(DefaultInputModule.class);
+    DefaultInputModule mod1 = mock(DefaultInputModule.class);
+    DefaultInputModule mod2 = mock(DefaultInputModule.class);
+    DefaultInputModule mod3 = mock(DefaultInputModule.class);
+
+    when(root.key()).thenReturn("root");
+    when(mod1.key()).thenReturn("mod1");
+    when(mod2.key()).thenReturn("mod2");
+    when(mod3.key()).thenReturn("mod3");
+    
+    when(root.getKeyWithBranch()).thenReturn("root");
+    when(mod1.getKeyWithBranch()).thenReturn("mod1");
+    when(mod2.getKeyWithBranch()).thenReturn("mod2");
+    when(mod3.getKeyWithBranch()).thenReturn("mod3");
+
+    when(root.definition()).thenReturn(rootDef);
+    when(mod1.definition()).thenReturn(def);
+    when(mod2.definition()).thenReturn(def);
+    when(mod3.definition()).thenReturn(def);
 
-    root.addSubProject(mod1);
-    mod1.addSubProject(mod2);
-    root.addSubProject(mod3);
-    root.addSubProject(mod4);
+    createIndexer(root);
+    when(moduleHierarchy.root()).thenReturn(root);
+    when(moduleHierarchy.children(root)).thenReturn(Arrays.asList(mod1, mod2, mod3));
 
-    when(reactor.getRoot()).thenReturn(root);
     indexer.start();
 
     InputModule rootModule = moduleHierarchy.root();
index cd628bbe8f48b6907a1acb9214d578bf53a5c8b3..af46b5edd7bc59a55e41e9af1d5ac57322e27061 100644 (file)
  */
 package org.sonar.scanner.scan;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import java.io.File;
 import java.nio.file.Files;
 import java.nio.file.Path;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
-import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.home.cache.DirectoryLock;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 public class ProjectLockTest {
   @Rule
   public TemporaryFolder tempFolder = new TemporaryFolder();
@@ -49,12 +50,12 @@ public class ProjectLockTest {
   }
 
   private ProjectLock setUpTest(File file) {
-    ProjectReactor projectReactor = mock(ProjectReactor.class);
-    ProjectDefinition projectDefinition = mock(ProjectDefinition.class);
-    when(projectReactor.getRoot()).thenReturn(projectDefinition);
-    when(projectDefinition.getWorkDir()).thenReturn(file);
+    InputModuleHierarchy hierarchy = mock(InputModuleHierarchy.class);
+    DefaultInputModule root = mock(DefaultInputModule.class);
+    when(hierarchy.root()).thenReturn(root);
+    when(root.getWorkDir()).thenReturn(file);
 
-    return new ProjectLock(projectReactor);
+    return new ProjectLock(hierarchy);
   }
 
   @Test
index 9f7833fbc7590788a23fa57ac0fce5fa60c2c531..24fa9205c37af29252b90def0da4c7335065fa68 100644 (file)
@@ -19,6 +19,9 @@
  */
 package org.sonar.scanner.scan;
 
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -26,28 +29,21 @@ import org.junit.rules.ExpectedException;
 import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.batch.bootstrap.ProjectReactor;
-import org.sonar.api.config.Settings;
-import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.utils.MessageException;
 import org.sonar.scanner.analysis.DefaultAnalysisMode;
 
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 public class ProjectReactorValidatorTest {
 
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
   private ProjectReactorValidator validator;
-  private Settings settings;
   private DefaultAnalysisMode mode;
 
   @Before
   public void prepare() {
     mode = mock(DefaultAnalysisMode.class);
-    settings = new MapSettings();
-    validator = new ProjectReactorValidator(settings, mode);
+    validator = new ProjectReactorValidator(mode);
   }
 
   @Test
@@ -62,12 +58,12 @@ public class ProjectReactorValidatorTest {
     validator.validate(createProjectReactor("3-3"));
     validator.validate(createProjectReactor("-:"));
   }
-  
+
   @Test
   public void allow_slash_issues_mode() {
     when(mode.isIssues()).thenReturn(true);
     validator.validate(createProjectReactor("project/key"));
-    
+
     when(mode.isIssues()).thenReturn(false);
     thrown.expect(MessageException.class);
     thrown.expectMessage("is not a valid project or module key");
@@ -157,16 +153,6 @@ public class ProjectReactorValidatorTest {
     validator.validate(reactor);
   }
 
-  @Test
-  public void fail_with_deprecated_sonar_phase() {
-    ProjectReactor reactor = createProjectReactor("foo");
-    settings.setProperty("sonar.phase", "phase");
-
-    thrown.expect(MessageException.class);
-    thrown.expectMessage("\"sonar.phase\" is deprecated");
-    validator.validate(reactor);
-  }
-
   private ProjectReactor createProjectReactor(String projectKey) {
     ProjectDefinition def = ProjectDefinition.create().setProperty(CoreProperties.PROJECT_KEY_PROPERTY, projectKey);
     ProjectReactor reactor = new ProjectReactor(def);
@@ -177,9 +163,7 @@ public class ProjectReactorValidatorTest {
     ProjectDefinition def = ProjectDefinition.create()
       .setProperty(CoreProperties.PROJECT_KEY_PROPERTY, projectKey)
       .setProperty(CoreProperties.PROJECT_BRANCH_PROPERTY, branch);
-    ProjectReactor reactor = new ProjectReactor(def);
-    settings.setProperty(CoreProperties.PROJECT_BRANCH_PROPERTY, branch);
-    return reactor;
+    return new ProjectReactor(def);
   }
 
 }
index 58b4acc282329557ff1cdbef72b55c3864ebefd6..aa824ab9abcf22d6d3a845cee3a9c6a7d4f3c730 100644 (file)
  */
 package org.sonar.scanner.scan;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import java.io.File;
 import java.io.IOException;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
-import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
+import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.home.cache.DirectoryLock;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 public class WorkDirectoryCleanerTest {
   private WorkDirectoryCleaner cleaner;
   @Rule
@@ -50,21 +51,21 @@ public class WorkDirectoryCleanerTest {
     lock.createNewFile();
 
     // mock project
-    ProjectReactor projectReactor = mock(ProjectReactor.class);
-    ProjectDefinition projectDefinition = mock(ProjectDefinition.class);
-    when(projectReactor.getRoot()).thenReturn(projectDefinition);
-    when(projectDefinition.getWorkDir()).thenReturn(temp.getRoot());
+    InputModuleHierarchy hierarchy = mock(InputModuleHierarchy.class);
+    DefaultInputModule root = mock(DefaultInputModule.class);
+    when(hierarchy.root()).thenReturn(root);
+    when(root.getWorkDir()).thenReturn(temp.getRoot());
 
     assertThat(temp.getRoot().list().length).isGreaterThan(1);
-    cleaner = new WorkDirectoryCleaner(projectReactor);
+    cleaner = new WorkDirectoryCleaner(hierarchy);
   }
-  
+
   @Test
   public void testNonExisting() {
     temp.delete();
     cleaner.execute();
   }
-  
+
   @Test
   public void testClean() {
     File lock = new File(temp.getRoot(), DirectoryLock.LOCK_FILE_NAME);
index 9815f2de7f22d4b9de89b376f2cc14da55c1554d..f64a672975036b3d67d8133f7dc4a55a54e69666 100644 (file)
  */
 package org.sonar.scanner.scan.filesystem;
 
+import static org.assertj.core.api.Assertions.assertThat;
+
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Path;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -33,8 +36,6 @@ import org.sonar.api.batch.fs.internal.DefaultIndexedFile;
 import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.scan.filesystem.FileExclusions;
 
-import static org.assertj.core.api.Assertions.assertThat;
-
 public class ExclusionFiltersTest {
 
   @Rule
@@ -53,7 +54,8 @@ public class ExclusionFiltersTest {
   @Test
   public void no_inclusions_nor_exclusions() throws IOException {
     filter.prepare();
-    IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/FooDao.java");
+
+    IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/FooDao.java", null);
     assertThat(filter.accept(indexedFile, InputFile.Type.MAIN)).isTrue();
     assertThat(filter.accept(indexedFile, InputFile.Type.TEST)).isTrue();
   }
@@ -63,10 +65,10 @@ public class ExclusionFiltersTest {
     settings.setProperty(CoreProperties.PROJECT_INCLUSIONS_PROPERTY, "**/*Dao.java");
     filter.prepare();
 
-    IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/FooDao.java");
+    IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/FooDao.java", null);
     assertThat(filter.accept(indexedFile, InputFile.Type.MAIN)).isTrue();
 
-    indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/Foo.java");
+    indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/Foo.java", null);
     assertThat(filter.accept(indexedFile, InputFile.Type.MAIN)).isFalse();
   }
 
@@ -75,10 +77,10 @@ public class ExclusionFiltersTest {
     settings.setProperty(CoreProperties.PROJECT_INCLUSIONS_PROPERTY, "**/*Dao.java,**/*Dto.java");
     filter.prepare();
 
-    IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/Foo.java");
+    IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/Foo.java", null);
     assertThat(filter.accept(indexedFile, InputFile.Type.MAIN)).isFalse();
 
-    indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/FooDto.java");
+    indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/FooDto.java", null);
     assertThat(filter.accept(indexedFile, InputFile.Type.MAIN)).isTrue();
   }
 
@@ -89,14 +91,14 @@ public class ExclusionFiltersTest {
     settings.setProperty(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, "**/*Dao.java");
     filter.prepare();
 
-    IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/FooDao.java");
+    IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/FooDao.java", null);
     assertThat(filter.accept(indexedFile, InputFile.Type.MAIN)).isFalse();
 
-    indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/Foo.java");
+    indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/com/mycompany/Foo.java", null);
     assertThat(filter.accept(indexedFile, InputFile.Type.MAIN)).isTrue();
 
     // source exclusions do not apply to tests
-    indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/test/java/com/mycompany/FooDao.java");
+    indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/test/java/com/mycompany/FooDao.java", null);
     assertThat(filter.accept(indexedFile, InputFile.Type.TEST)).isTrue();
   }
 
@@ -108,10 +110,10 @@ public class ExclusionFiltersTest {
     settings.setProperty(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY, "file:" + excludedFile.getAbsolutePath());
     filter.prepare();
 
-    IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/org/bar/Foo.java");
+    IndexedFile indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/org/bar/Foo.java", null);
     assertThat(filter.accept(indexedFile, InputFile.Type.MAIN)).isTrue();
 
-    indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/org/bar/Bar.java");
+    indexedFile = new DefaultIndexedFile("foo", moduleBaseDir, "src/main/java/org/bar/Bar.java", null);
     assertThat(filter.accept(indexedFile, InputFile.Type.MAIN)).isFalse();
   }
 
index 6bff097acf38d20a3cd237b0baffcfd3afb87f0e..9a82a623f55df211d751103097a620d1e070fc50 100644 (file)
@@ -27,6 +27,7 @@ import java.util.List;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.batch.fs.InputFile.Status;
 import org.sonar.api.batch.fs.InputFile.Type;
@@ -44,16 +45,20 @@ public class InputComponentStoreTest {
 
   @Test
   public void should_add_input_file() throws Exception {
-    InputComponentStore cache = new InputComponentStore(new PathResolver());
-
     String rootModuleKey = "struts";
+    String subModuleKey = "struts-core";
+
     File rootBaseDir = temp.newFolder();
-    DefaultInputModule rootModule = TestInputFileBuilder.newDefaultInputModule(rootModuleKey, rootBaseDir);
-    cache.put(rootModule);
 
-    String subModuleKey = "struts-core";
-    DefaultInputModule subModule = TestInputFileBuilder.newDefaultInputModule(subModuleKey, temp.newFolder());
-    rootModule.definition().addSubProject(subModule.definition());
+    ProjectDefinition moduleDef = ProjectDefinition.create()
+      .setKey(subModuleKey).setBaseDir(rootBaseDir);
+    ProjectDefinition rootDef = ProjectDefinition.create()
+      .setKey(rootModuleKey).setBaseDir(rootBaseDir).addSubProject(moduleDef);
+
+    DefaultInputModule rootModule = TestInputFileBuilder.newDefaultInputModule(rootDef);
+    DefaultInputModule subModule = TestInputFileBuilder.newDefaultInputModule(moduleDef);
+
+    InputComponentStore cache = new InputComponentStore(new PathResolver(), rootModule);
     cache.put(subModule);
 
     DefaultInputFile fooFile = new TestInputFileBuilder(rootModuleKey, "src/main/java/Foo.java")
@@ -97,9 +102,7 @@ public class InputComponentStoreTest {
 
   static class InputComponentStoreTester extends InputComponentStore {
     InputComponentStoreTester() throws IOException {
-      super(new PathResolver());
-      DefaultInputModule root = TestInputFileBuilder.newDefaultInputModule("root", temp.newFolder());
-      put(root);
+      super(new PathResolver(), TestInputFileBuilder.newDefaultInputModule("root", temp.newFolder()));
     }
 
     InputFile addFile(String moduleKey, String relpath, String language) {
index 86d33bb66608cb60019c26c9144c563e1e404eb3..1d8f553d1f5bbab7ac9be5ccb1850047a842c458 100644 (file)
  */
 package org.sonar.scanner.scan.filesystem;
 
+import static junit.framework.Assert.fail;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.spy;
+
 import java.io.File;
 import java.io.IOException;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 import org.sonar.api.CoreProperties;
-import org.sonar.api.batch.fs.internal.DefaultIndexedFile;
 import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.resources.Language;
 import org.sonar.api.resources.Languages;
@@ -35,10 +39,6 @@ import org.sonar.api.utils.MessageException;
 import org.sonar.scanner.repository.language.DefaultLanguagesRepository;
 import org.sonar.scanner.repository.language.LanguagesRepository;
 
-import static junit.framework.Assert.fail;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.spy;
-
 public class LanguageDetectionTest {
 
   @Rule
@@ -67,23 +67,23 @@ public class LanguageDetectionTest {
     LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(new MockLanguage("java", "java", "jav"), new MockLanguage("cobol", "cbl", "cob")));
     LanguageDetection detection = new LanguageDetection(settings.asConfig(), languages);
 
-    assertThat(detection.language(newIndexedFile("Foo.java"))).isEqualTo("java");
-    assertThat(detection.language(newIndexedFile("src/Foo.java"))).isEqualTo("java");
-    assertThat(detection.language(newIndexedFile("Foo.JAVA"))).isEqualTo("java");
-    assertThat(detection.language(newIndexedFile("Foo.jav"))).isEqualTo("java");
-    assertThat(detection.language(newIndexedFile("Foo.Jav"))).isEqualTo("java");
+    assertThat(detectLanguage(detection, "Foo.java")).isEqualTo("java");
+    assertThat(detectLanguage(detection, "src/Foo.java")).isEqualTo("java");
+    assertThat(detectLanguage(detection, "Foo.JAVA")).isEqualTo("java");
+    assertThat(detectLanguage(detection, "Foo.jav")).isEqualTo("java");
+    assertThat(detectLanguage(detection, "Foo.Jav")).isEqualTo("java");
 
-    assertThat(detection.language(newIndexedFile("abc.cbl"))).isEqualTo("cobol");
-    assertThat(detection.language(newIndexedFile("abc.CBL"))).isEqualTo("cobol");
+    assertThat(detectLanguage(detection, "abc.cbl")).isEqualTo("cobol");
+    assertThat(detectLanguage(detection, "abc.CBL")).isEqualTo("cobol");
 
-    assertThat(detection.language(newIndexedFile("abc.php"))).isNull();
-    assertThat(detection.language(newIndexedFile("abc"))).isNull();
+    assertThat(detectLanguage(detection, "abc.php")).isNull();
+    assertThat(detectLanguage(detection, "abc")).isNull();
   }
 
   @Test
   public void should_not_fail_if_no_language() throws Exception {
     LanguageDetection detection = spy(new LanguageDetection(settings.asConfig(), new DefaultLanguagesRepository(new Languages())));
-    assertThat(detection.language(newIndexedFile("Foo.java"))).isNull();
+    assertThat(detectLanguage(detection, "Foo.java")).isNull();
   }
 
   @Test
@@ -91,7 +91,7 @@ public class LanguageDetectionTest {
     LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(new MockLanguage("abap", "abap", "ABAP")));
 
     LanguageDetection detection = new LanguageDetection(settings.asConfig(), languages);
-    assertThat(detection.language(newIndexedFile("abc.abap"))).isEqualTo("abap");
+    assertThat(detectLanguage(detection, "abc.abap")).isEqualTo("abap");
   }
 
   @Test
@@ -102,15 +102,15 @@ public class LanguageDetectionTest {
 
     // No side-effect on non-ABAP projects
     LanguageDetection detection = new LanguageDetection(settings.asConfig(), languages);
-    assertThat(detection.language(newIndexedFile("abc"))).isNull();
-    assertThat(detection.language(newIndexedFile("abc.abap"))).isNull();
-    assertThat(detection.language(newIndexedFile("abc.java"))).isEqualTo("java");
+    assertThat(detectLanguage(detection, "abc")).isNull();
+    assertThat(detectLanguage(detection, "abc.abap")).isNull();
+    assertThat(detectLanguage(detection, "abc.java")).isEqualTo("java");
 
     settings.setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, "abap");
     detection = new LanguageDetection(settings.asConfig(), languages);
-    assertThat(detection.language(newIndexedFile("abc"))).isEqualTo("abap");
-    assertThat(detection.language(newIndexedFile("abc.txt"))).isEqualTo("abap");
-    assertThat(detection.language(newIndexedFile("abc.java"))).isEqualTo("abap");
+    assertThat(detectLanguage(detection, "abc")).isEqualTo("abap");
+    assertThat(detectLanguage(detection, "abc.txt")).isEqualTo("abap");
+    assertThat(detectLanguage(detection, "abc.java")).isEqualTo("abap");
   }
 
   @Test
@@ -119,10 +119,10 @@ public class LanguageDetectionTest {
 
     settings.setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, "java");
     LanguageDetection detection = new LanguageDetection(settings.asConfig(), languages);
-    assertThat(detection.language(newIndexedFile("abc"))).isNull();
-    assertThat(detection.language(newIndexedFile("abc.php"))).isNull();
-    assertThat(detection.language(newIndexedFile("abc.java"))).isEqualTo("java");
-    assertThat(detection.language(newIndexedFile("src/abc.java"))).isEqualTo("java");
+    assertThat(detectLanguage(detection, "abc")).isNull();
+    assertThat(detectLanguage(detection, "abc.php")).isNull();
+    assertThat(detectLanguage(detection, "abc.java")).isEqualTo("java");
+    assertThat(detectLanguage(detection, "src/abc.java")).isEqualTo("java");
   }
 
   @Test
@@ -140,7 +140,7 @@ public class LanguageDetectionTest {
     LanguagesRepository languages = new DefaultLanguagesRepository(new Languages(new MockLanguage("xml", "xhtml"), new MockLanguage("web", "xhtml")));
     LanguageDetection detection = new LanguageDetection(settings.asConfig(), languages);
     try {
-      detection.language(newIndexedFile("abc.xhtml"));
+      detectLanguage(detection, "abc.xhtml");
       fail();
     } catch (MessageException e) {
       assertThat(e.getMessage())
@@ -157,8 +157,8 @@ public class LanguageDetectionTest {
     settings.setProperty("sonar.lang.patterns.xml", "xml/**");
     settings.setProperty("sonar.lang.patterns.web", "web/**");
     LanguageDetection detection = new LanguageDetection(settings.asConfig(), languages);
-    assertThat(detection.language(newIndexedFile("xml/abc.xhtml"))).isEqualTo("xml");
-    assertThat(detection.language(newIndexedFile("web/abc.xhtml"))).isEqualTo("web");
+    assertThat(detectLanguage(detection, "xml/abc.xhtml")).isEqualTo("xml");
+    assertThat(detectLanguage(detection, "web/abc.xhtml")).isEqualTo("web");
   }
 
   @Test
@@ -169,10 +169,10 @@ public class LanguageDetectionTest {
 
     LanguageDetection detection = new LanguageDetection(settings.asConfig(), languages);
 
-    assertThat(detection.language(newIndexedFile("abc.abap"))).isEqualTo("abap");
-    assertThat(detection.language(newIndexedFile("abc.cobol"))).isEqualTo("cobol");
+    assertThat(detectLanguage(detection, "abc.abap")).isEqualTo("abap");
+    assertThat(detectLanguage(detection, "abc.cobol")).isEqualTo("cobol");
     try {
-      detection.language(newIndexedFile("abc.txt"));
+      detectLanguage(detection, "abc.txt");
       fail();
     } catch (MessageException e) {
       assertThat(e.getMessage())
@@ -182,9 +182,8 @@ public class LanguageDetectionTest {
     }
   }
 
-  private DefaultIndexedFile newIndexedFile(String path) throws IOException {
-    File basedir = temp.newFolder();
-    return new DefaultIndexedFile("foo", basedir.toPath(), path);
+  private String detectLanguage(LanguageDetection detection, String path) {
+    return detection.language(new File(temp.getRoot(), path).getAbsolutePath(), path);
   }
 
   static class MockLanguage implements Language {
index 4790f48ae6e2fd38bfaa71324fbf6ac57075dad3..454223ad846fe46a8accb55ee5e6dd684641f986 100644 (file)
  */
 package org.sonar.scanner.scan.filesystem;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
 import java.io.File;
 import java.io.IOException;
+
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.FilenameUtils;
 import org.junit.Rule;
@@ -30,9 +34,6 @@ import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.scan.filesystem.PathResolver;
 import org.sonar.api.utils.TempFolder;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-
 public class ModuleFileSystemInitializerTest {
 
   @Rule
index cb018de8258cd08af97a9d8169b3dd38595b64ff..8451685ee3c3e033132316e66b45aaf37ae97cc9 100644 (file)
@@ -26,6 +26,7 @@ import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.batch.fs.InputModule;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
 import org.sonar.api.scan.filesystem.PathResolver;
 import org.sonar.scanner.sensor.SensorStrategy;
@@ -46,8 +47,8 @@ public class ModuleInputComponentStoreTest {
 
   @Before
   public void setUp() throws IOException {
-    componentStore = new InputComponentStore(new PathResolver());
-    componentStore.put(TestInputFileBuilder.newDefaultInputModule(moduleKey, temp.newFolder()));
+    DefaultInputModule root = TestInputFileBuilder.newDefaultInputModule(moduleKey, temp.newFolder());
+    componentStore = new InputComponentStore(new PathResolver(), root);
   }
 
   @Test
index 926b149ef3e66603e110982ae41a5a60876e2207..42d4c443c72b9da5f35fd593d597e3b9bea1ede9 100644 (file)
  */
 package org.sonar.scanner.scan.report;
 
+import static net.javacrumbs.jsonunit.assertj.JsonAssert.assertThatJson;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.StringWriter;
@@ -26,6 +32,7 @@ import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.TimeZone;
+
 import org.apache.commons.io.IOUtils;
 import org.junit.Before;
 import org.junit.Rule;
@@ -51,12 +58,6 @@ import org.sonar.scanner.issue.tracking.TrackedIssue;
 import org.sonar.scanner.scan.DefaultComponentTree;
 import org.sonar.scanner.scan.filesystem.InputComponentStore;
 
-import static net.javacrumbs.jsonunit.assertj.JsonAssert.assertThatJson;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
 public class JSONReportTest {
 
   private SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
@@ -80,10 +81,11 @@ public class JSONReportTest {
     SIMPLE_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+02:00"));
     when(server.getVersion()).thenReturn("3.6");
 
-    InputComponentStore inputComponentStore = new InputComponentStore(new PathResolver());
     DefaultComponentTree inputComponentTree = new DefaultComponentTree();
-    DefaultInputModule rootModule = new DefaultInputModule(ProjectDefinition.create().setBaseDir(projectBaseDir).setKey("struts"), 1);
-    inputComponentStore.put(rootModule);
+    ProjectDefinition def = ProjectDefinition.create().setBaseDir(projectBaseDir).setKey("struts");
+    DefaultInputModule rootModule = new DefaultInputModule(def, 1);
+    InputComponentStore inputComponentStore = new InputComponentStore(new PathResolver(), rootModule);
+
     DefaultInputModule moduleA = new DefaultInputModule("struts-core");
     inputComponentTree.index(moduleA, rootModule);
     DefaultInputModule moduleB = new DefaultInputModule("struts-ui");
index 7eafee529effaa945c7ca25553d86f639de1c32b..e5d5d91a7a2e5b632b9a1344637f05f21fbee0f1 100644 (file)
@@ -37,14 +37,13 @@ public class CoverageExclusionsTest {
   @Before
   public void prepare() {
     settings = new MapSettings(new PropertyDefinitions(ExclusionProperties.all()));
-    coverageExclusions = new CoverageExclusions(settings.asConfig());
   }
 
   @Test
   public void shouldExcludeFileBasedOnPattern() {
     InputFile file = new TestInputFileBuilder("foo", "src/org/polop/File.php").build();
     settings.setProperty("sonar.coverage.exclusions", "src/org/polop/*");
-    coverageExclusions.initPatterns();
+    coverageExclusions = new CoverageExclusions(settings.asConfig());
     assertThat(coverageExclusions.isExcluded(file)).isTrue();
   }
 
@@ -52,7 +51,7 @@ public class CoverageExclusionsTest {
   public void shouldNotExcludeFileBasedOnPattern() {
     InputFile file = new TestInputFileBuilder("foo", "src/org/polop/File.php").build();
     settings.setProperty("sonar.coverage.exclusions", "src/org/other/*");
-    coverageExclusions.initPatterns();
+    coverageExclusions = new CoverageExclusions(settings.asConfig());
     assertThat(coverageExclusions.isExcluded(file)).isFalse();
   }
 }
index f61d225128daaaa40a9b7a51f151c91f9824f75d..e8468cc79d32da443a38bd456d98e762de3cfcfe 100644 (file)
@@ -21,9 +21,12 @@ package org.sonar.scanner.protocol.output;
 
 import java.io.File;
 
+import javax.annotation.concurrent.Immutable;
+
 /**
  * Structure of files in the zipped report
  */
+@Immutable
 public class FileStructure {
 
   public enum Domain {
index 9cca4f6d5ca56f747d09f52788512ec99e9634ea..9ce6547ff5230ee06532e54cf8265c3d3a895f38 100644 (file)
@@ -23,9 +23,13 @@ import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.OutputStream;
+
+import javax.annotation.concurrent.Immutable;
+
 import org.sonar.core.util.ContextException;
 import org.sonar.core.util.Protobuf;
 
+@Immutable
 public class ScannerReportWriter {
 
   private final FileStructure fileStructure;