]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11507 WS /batch/project new file structure
authorMichal Duda <michalno1@gmail.com>
Thu, 22 Nov 2018 17:09:44 +0000 (18:09 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 16 Jan 2019 08:43:02 +0000 (09:43 +0100)
25 files changed:
server/sonar-server/src/main/java/org/sonar/server/batch/ProjectAction.java
server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataLoader.java
server/sonar-server/src/test/java/org/sonar/server/batch/ProjectActionTest.java
server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderTest.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/DefaultProjectRepositoriesLoader.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/MultiModuleProjectRepository.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/ProjectRepositories.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/SingleProjectRepository.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/StatusDetection.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmPublisher.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/branch/BranchMediumTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issuesmode/ScanOnlyChangedTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/scm/ScmMediumTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/DefaultProjectRepositoriesLoaderTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/MultiModuleProjectRepositoryTest.java [new file with mode: 0644]
sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/ProjectRepositoriesProviderTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/SingleProjectRepositoryTest.java [new file with mode: 0644]
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/StatusDetectionTest.java
sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/MultiModuleProjectRepository.java [new file with mode: 0644]
sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/ProjectRepositories.java
sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/SingleProjectRepository.java [new file with mode: 0644]
sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/MultiModuleProjectRepositoryTest.java [new file with mode: 0644]
sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/SingleProjectRepositoryTest.java [new file with mode: 0644]
sonar-ws/src/main/protobuf/ws-batch.proto

index 35d8f53b231c408203b2695aa49d2745d591160b..5e8cfb22de84a92596c10bae0bd654df5abb3aa3 100644 (file)
  */
 package org.sonar.server.batch;
 
+import com.google.common.collect.Maps;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.Map;
+import java.util.stream.Collectors;
 import org.sonar.api.server.ws.Change;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.scanner.protocol.input.FileData;
+import org.sonar.scanner.protocol.input.MultiModuleProjectRepository;
 import org.sonar.scanner.protocol.input.ProjectRepositories;
+import org.sonar.scanner.protocol.input.SingleProjectRepository;
 import org.sonarqube.ws.Batch.WsProjectResponse;
 import org.sonarqube.ws.Batch.WsProjectResponse.FileData.Builder;
 
@@ -109,33 +112,31 @@ public class ProjectAction implements BatchWsAction {
     WsProjectResponse.Builder response = WsProjectResponse.newBuilder();
     setNullable(data.lastAnalysisDate(), response::setLastAnalysisDate, Date::getTime);
     response.setTimestamp(data.timestamp());
-    response.getMutableFileDataByModuleAndPath()
-      .putAll(buildFileDataByModuleAndPath(data));
+    if (data instanceof SingleProjectRepository) {
+      response.putAllFileDataByPath(buildFileDataByPath((SingleProjectRepository) data));
+    } else {
+      response.putAllFileDataByModuleAndPath(buildFileDataByModuleAndPath((MultiModuleProjectRepository) data));
+    }
 
     return response.build();
   }
 
-  private static Map<String, WsProjectResponse.FileDataByPath> buildFileDataByModuleAndPath(ProjectRepositories data) {
-    Map<String, WsProjectResponse.FileDataByPath> fileDataByModuleAndPathResponse = new HashMap<>();
-    for (Map.Entry<String, Map<String, FileData>> moduleAndFileDataByPathEntry : data.fileDataByModuleAndPath().entrySet()) {
-      fileDataByModuleAndPathResponse.put(
-        moduleAndFileDataByPathEntry.getKey(),
-        buildFileDataByPath(moduleAndFileDataByPathEntry.getValue()));
-    }
+  private static Map<String, WsProjectResponse.FileDataByPath> buildFileDataByModuleAndPath(MultiModuleProjectRepository data) {
+    return data.repositoriesByModule().entrySet()
+      .stream()
+      .map(entry -> Maps.immutableEntry(entry.getKey(), buildFileDataByPath(entry.getValue().fileData())))
+      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+  }
 
-    return fileDataByModuleAndPathResponse;
+  private static Map<String, WsProjectResponse.FileData> buildFileDataByPath(SingleProjectRepository data) {
+    return data.fileData().entrySet()
+      .stream()
+      .collect(Collectors.toMap(Map.Entry::getKey, e -> toFileDataResponse(e.getValue())));
   }
 
   private static WsProjectResponse.FileDataByPath buildFileDataByPath(Map<String, FileData> fileDataByPath) {
     WsProjectResponse.FileDataByPath.Builder response = WsProjectResponse.FileDataByPath.newBuilder();
-    Map<String, WsProjectResponse.FileData> fileDataByPathResponse = response.getMutableFileDataByPath();
-
-    for (Map.Entry<String, FileData> pathFileDataEntry : fileDataByPath.entrySet()) {
-      fileDataByPathResponse.put(
-        pathFileDataEntry.getKey(),
-        toFileDataResponse(pathFileDataEntry.getValue()));
-    }
-
+    fileDataByPath.forEach((key, value) -> response.putFileDataByPath(key, toFileDataResponse(value)));
     return response.build();
   }
 
index 7808454455abb406b3a7143b79ed4032bf3fefa7..53cdb5b81a275e85aa56222d19c4cc041412c34e 100644 (file)
@@ -32,7 +32,9 @@ import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.FilePathWithHashDto;
 import org.sonar.db.permission.OrganizationPermission;
 import org.sonar.scanner.protocol.input.FileData;
+import org.sonar.scanner.protocol.input.MultiModuleProjectRepository;
 import org.sonar.scanner.protocol.input.ProjectRepositories;
+import org.sonar.scanner.protocol.input.SingleProjectRepository;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.user.UserSession;
@@ -56,7 +58,6 @@ public class ProjectDataLoader {
 
   public ProjectRepositories load(ProjectDataQuery query) {
     try (DbSession session = dbClient.openSession(false)) {
-      ProjectRepositories data = new ProjectRepositories();
       String projectKey = query.getProjectKey();
       String branch = query.getBranch();
       String pullRequest = query.getPullRequest();
@@ -72,12 +73,23 @@ public class ProjectDataLoader {
       List<ComponentDto> modulesTree = dbClient.componentDao().selectEnabledDescendantModules(session, branchOrMainModule.uuid());
 
       List<FilePathWithHashDto> files = searchFilesWithHashAndRevision(session, branchOrMainModule);
-      addFileData(data, modulesTree, files);
+
+      // MMF-365 we still have to support multi-module projects because it's not possible to transform from logical to
+      // physical structure for some multi-module projects
+      ProjectRepositories data;
+      if (modulesTree.size() > 1) {
+        MultiModuleProjectRepository repository = new MultiModuleProjectRepository();
+        addFileDataPerModule(repository, modulesTree, files);
+        data = repository;
+      } else {
+        SingleProjectRepository repository = new SingleProjectRepository();
+        addFileData(repository, files);
+        data = repository;
+      }
 
       // FIXME need real value but actually only used to know if there is a previous analysis in local issue tracking mode so any value is
       // ok
       data.setLastAnalysisDate(new Date());
-
       return data;
     }
   }
@@ -90,7 +102,7 @@ public class ProjectDataLoader {
       : dbClient.componentDao().selectEnabledDescendantFiles(session, module.uuid());
   }
 
-  private static void addFileData(ProjectRepositories data, List<ComponentDto> moduleChildren, List<FilePathWithHashDto> files) {
+  private static void addFileDataPerModule(MultiModuleProjectRepository data, List<ComponentDto> moduleChildren, List<FilePathWithHashDto> files) {
     Map<String, String> moduleKeysByUuid = newHashMap();
     for (ComponentDto module : moduleChildren) {
       moduleKeysByUuid.put(module.uuid(), module.getKey());
@@ -98,7 +110,14 @@ public class ProjectDataLoader {
 
     for (FilePathWithHashDto file : files) {
       FileData fileData = new FileData(file.getSrcHash(), file.getRevision());
-      data.addFileData(moduleKeysByUuid.get(file.getModuleUuid()), file.getPath(), fileData);
+      data.addFileDataToModule(moduleKeysByUuid.get(file.getModuleUuid()), file.getPath(), fileData);
+    }
+  }
+
+  private static void addFileData(SingleProjectRepository data, List<FilePathWithHashDto> files) {
+    for (FilePathWithHashDto file : files) {
+      FileData fileData = new FileData(file.getSrcHash(), file.getRevision());
+      data.addFileData(file.getPath(), fileData);
     }
   }
 
index daa57b7d5729621f73f611a487fd653398625046..16d8c0de13957ff362a57cc2cca89355d44a3a0d 100644 (file)
@@ -22,7 +22,9 @@ package org.sonar.server.batch;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.sonar.scanner.protocol.input.FileData;
+import org.sonar.scanner.protocol.input.MultiModuleProjectRepository;
 import org.sonar.scanner.protocol.input.ProjectRepositories;
+import org.sonar.scanner.protocol.input.SingleProjectRepository;
 import org.sonar.server.ws.TestResponse;
 import org.sonar.server.ws.WsActionTester;
 import org.sonarqube.ws.Batch.WsProjectResponse;
@@ -42,7 +44,7 @@ public class ProjectActionTest {
   public void project_referentials() {
     String projectKey = "org.codehaus.sonar:sonar";
 
-    ProjectRepositories projectReferentials = mock(ProjectRepositories.class);
+    ProjectRepositories projectReferentials = mock(SingleProjectRepository.class);
 
     ArgumentCaptor<ProjectDataQuery> queryArgumentCaptor = ArgumentCaptor.forClass(ProjectDataQuery.class);
     when(projectDataLoader.load(queryArgumentCaptor.capture())).thenReturn(projectReferentials);
@@ -53,7 +55,7 @@ public class ProjectActionTest {
       .setParam("profile", "Default")
       .setParam("preview", "false")
       .execute();
-    assertJson(response.getInput()).isSimilarTo("{\"fileDataByModuleAndPath\": {}}");
+    assertJson(response.getInput()).isSimilarTo("{\"fileDataByPath\": {}}");
 
     assertThat(queryArgumentCaptor.getValue().getProjectKey()).isEqualTo(projectKey);
     assertThat(queryArgumentCaptor.getValue().getProfileName()).isEqualTo("Default");
@@ -68,13 +70,55 @@ public class ProjectActionTest {
   public void do_not_fail_when_a_path_is_null() {
     String projectKey = "org.codehaus.sonar:sonar";
 
-    ProjectRepositories projectRepositories = new ProjectRepositories().addFileData("module-1", null, new FileData(null, null));
+    ProjectRepositories projectRepositories = new MultiModuleProjectRepository()
+      .addFileDataToModule("module-1", null, new FileData(null, null));
     when(projectDataLoader.load(any(ProjectDataQuery.class))).thenReturn(projectRepositories);
 
     WsProjectResponse wsProjectResponse = ws.newRequest()
       .setParam("key", projectKey)
       .setParam("profile", "Default")
       .executeProtobuf(WsProjectResponse.class);
-    assertThat(wsProjectResponse.getFileDataByModuleAndPath()).isEmpty();
+    assertThat(wsProjectResponse.getFileDataByModuleAndPathMap()).isEmpty();
+  }
+
+  @Test
+  public void use_new_file_structure_for_projects_without_submodules() {
+    String projectKey = "org.codehaus.sonar:sonar";
+
+    ProjectRepositories projectRepositories = new SingleProjectRepository()
+      .addFileData("src/main/java/SomeClass.java", new FileData("789456", "123456789"));
+    when(projectDataLoader.load(any(ProjectDataQuery.class))).thenReturn(projectRepositories);
+
+    WsProjectResponse wsProjectResponse = ws.newRequest()
+      .setParam("key", projectKey)
+      .setParam("profile", "Default")
+      .executeProtobuf(WsProjectResponse.class);
+    assertThat(wsProjectResponse.getFileDataByModuleAndPathMap()).isEmpty();
+    assertThat(wsProjectResponse.getFileDataByPathCount()).isEqualTo(1);
+    assertThat(wsProjectResponse.getFileDataByPathMap().get("src/main/java/SomeClass.java")).isNotNull();
+  }
+
+  @Test
+  public void use_old_file_structure_for_projects_with_submodules() {
+    String projectKey = "org.codehaus.sonar:sonar";
+
+    ProjectRepositories projectRepositories = new MultiModuleProjectRepository()
+      .addFileDataToModule("module-1", "src/main/java/SomeClass.java", new FileData("789456", "123456789"));
+    when(projectDataLoader.load(any(ProjectDataQuery.class))).thenReturn(projectRepositories);
+
+    WsProjectResponse wsProjectResponse = ws.newRequest()
+      .setParam("key", projectKey)
+      .setParam("profile", "Default")
+      .executeProtobuf(WsProjectResponse.class);
+
+    assertThat(wsProjectResponse.getFileDataByPathMap()).isEmpty();
+    assertThat(wsProjectResponse.getFileDataByModuleAndPathCount()).isEqualTo(1);
+    WsProjectResponse.FileDataByPath moduleData = wsProjectResponse.getFileDataByModuleAndPathMap().get("module-1");
+    assertThat(moduleData).isNotNull();
+    assertThat(moduleData.getFileDataByPathCount()).isEqualTo(1);
+    WsProjectResponse.FileData fileData = moduleData.getFileDataByPathMap().get("src/main/java/SomeClass.java");
+    assertThat(fileData).isNotNull();
+    assertThat(fileData.getHash()).isEqualTo("789456");
+    assertThat(fileData.getRevision()).isEqualTo("123456789");
   }
 }
index 3b950cd23a41a84c63f5d344d4b509b1049a506c..c7efa7745f03890fa9968dedf860499b850e9eb7 100644 (file)
@@ -34,7 +34,9 @@ import org.sonar.db.component.ResourceTypesRule;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.source.FileSourceDto;
 import org.sonar.scanner.protocol.input.FileData;
+import org.sonar.scanner.protocol.input.MultiModuleProjectRepository;
 import org.sonar.scanner.protocol.input.ProjectRepositories;
+import org.sonar.scanner.protocol.input.SingleProjectRepository;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.ForbiddenException;
@@ -44,6 +46,7 @@ import org.sonar.server.tester.UserSessionRule;
 import static com.google.common.collect.ImmutableList.of;
 import static java.lang.String.format;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
@@ -89,8 +92,11 @@ public class ProjectDataLoaderTest {
 
     ProjectRepositories ref = underTest.load(ProjectDataQuery.create().setProjectKey(project.getKey()));
 
-    assertThat(ref.fileDataByPath(project.getKey())).hasSize(1);
-    FileData fileData = ref.fileData(project.getKey(), file.path());
+    assertTrue(ref instanceof SingleProjectRepository);
+    SingleProjectRepository singleProjectRepository = ((SingleProjectRepository) ref);
+    assertThat(singleProjectRepository.fileData()).hasSize(1);
+    FileData fileData = singleProjectRepository.fileDataByPath(file.path());
+    assertThat(fileData).isNotNull();
     assertThat(fileData.hash()).isEqualTo("123456");
   }
 
@@ -110,8 +116,10 @@ public class ProjectDataLoaderTest {
 
     ProjectRepositories ref = underTest.load(ProjectDataQuery.create().setProjectKey(project.getKey()));
 
-    assertThat(ref.fileData(project.getKey(), projectFile.path()).hash()).isEqualTo("123456");
-    assertThat(ref.fileData(module.getKey(), moduleFile.path()).hash()).isEqualTo("789456");
+    assertTrue(ref instanceof MultiModuleProjectRepository);
+    MultiModuleProjectRepository repository = ((MultiModuleProjectRepository) ref);
+    assertThat(repository.fileData(project.getKey(), projectFile.path()).hash()).isEqualTo("123456");
+    assertThat(repository.fileData(module.getKey(), moduleFile.path()).hash()).isEqualTo("789456");
   }
 
   @Test
@@ -133,8 +141,10 @@ public class ProjectDataLoaderTest {
       .setProjectKey(project.getKey())
       .setBranch("my_branch"));
 
-    assertThat(ref.fileData(branch.getKey(), projectFile.path()).hash()).isEqualTo("123456");
-    assertThat(ref.fileData(moduleBranch.getKey(), moduleFile.path()).hash()).isEqualTo("789456");
+    assertTrue(ref instanceof MultiModuleProjectRepository);
+    MultiModuleProjectRepository repository = ((MultiModuleProjectRepository) ref);
+    assertThat(repository.fileData(branch.getKey(), projectFile.path()).hash()).isEqualTo("123456");
+    assertThat(repository.fileData(moduleBranch.getKey(), moduleFile.path()).hash()).isEqualTo("789456");
   }
 
   @Test
index 043264de8b3f51aa69e7e1d67b99b51423e0768a..271b84bceeea79d208e18297fc4f078c96893b18 100644 (file)
 package org.sonar.scanner.repository;
 
 import com.google.common.base.Throwables;
-import com.google.common.collect.HashBasedTable;
-import com.google.common.collect.Table;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.HttpURLConnection;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.Map;
 import javax.annotation.Nullable;
 import org.apache.commons.io.IOUtils;
@@ -34,9 +33,7 @@ import org.slf4j.LoggerFactory;
 import org.sonar.api.utils.MessageException;
 import org.sonar.scanner.bootstrap.ScannerWsClient;
 import org.sonar.scanner.util.ScannerUtils;
-import org.sonarqube.ws.Batch;
 import org.sonarqube.ws.Batch.WsProjectResponse;
-import org.sonarqube.ws.Batch.WsProjectResponse.FileDataByPath;
 import org.sonarqube.ws.client.GetRequest;
 import org.sonarqube.ws.client.HttpException;
 import org.sonarqube.ws.client.WsResponse;
@@ -62,7 +59,7 @@ public class DefaultProjectRepositoriesLoader implements ProjectRepositoriesLoad
       }
 
       LOG.debug("Project repository not available - continuing without it");
-      return new ProjectRepositories();
+      return new SingleProjectRepository();
     }
   }
 
@@ -97,22 +94,32 @@ public class DefaultProjectRepositoriesLoader implements ProjectRepositoriesLoad
   private static ProjectRepositories processStream(InputStream is, String projectKey) {
     try {
       WsProjectResponse response = WsProjectResponse.parseFrom(is);
-
-      Table<String, String, FileData> fileDataTable = HashBasedTable.create();
-
-      Map<String, FileDataByPath> fileDataByModuleAndPath = response.getFileDataByModuleAndPath();
-      for (Map.Entry<String, FileDataByPath> e1 : fileDataByModuleAndPath.entrySet()) {
-        for (Map.Entry<String, Batch.WsProjectResponse.FileData> e2 : e1.getValue().getFileDataByPath().entrySet()) {
-          FileData fd = new FileData(e2.getValue().getHash(), e2.getValue().getRevision());
-          fileDataTable.put(e1.getKey(), e2.getKey(), fd);
-        }
+      if (response.getFileDataByModuleAndPathCount() == 0) {
+        return new SingleProjectRepository(constructFileDataMap(response.getFileDataByPathMap()), new Date(response.getLastAnalysisDate()));
+      } else {
+        final Map<String, SingleProjectRepository> repositoriesPerModule = new HashMap<>();
+        response.getFileDataByModuleAndPathMap().keySet().forEach(moduleKey -> {
+          WsProjectResponse.FileDataByPath filePaths = response.getFileDataByModuleAndPathMap().get(moduleKey);
+          repositoriesPerModule.put(moduleKey, new SingleProjectRepository(
+            constructFileDataMap(filePaths.getFileDataByPathMap()), new Date(response.getLastAnalysisDate())));
+        });
+        return new MultiModuleProjectRepository(repositoriesPerModule, new Date(response.getLastAnalysisDate()));
       }
 
-      return new ProjectRepositories(fileDataTable, new Date(response.getLastAnalysisDate()));
     } catch (IOException e) {
       throw new IllegalStateException("Couldn't load project repository for " + projectKey, e);
     } finally {
       IOUtils.closeQuietly(is);
     }
   }
+
+  private static Map<String, FileData> constructFileDataMap(Map<String, WsProjectResponse.FileData> content) {
+    Map<String, FileData> fileDataMap = new HashMap<>();
+    content.forEach((key, value) -> {
+      FileData fd = new FileData(value.getHash(), value.getRevision());
+      fileDataMap.put(key, fd);
+    });
+
+    return fileDataMap;
+  }
 }
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/MultiModuleProjectRepository.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/MultiModuleProjectRepository.java
new file mode 100644 (file)
index 0000000..6876d63
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.repository;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Date;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+@Immutable
+public class MultiModuleProjectRepository extends ProjectRepositories {
+
+  private Map<String, SingleProjectRepository> repositoriesPerModule;
+
+  public MultiModuleProjectRepository(Map<String, SingleProjectRepository> repositoriesPerModule, @Nullable Date lastAnalysisDate) {
+    super(lastAnalysisDate, true);
+    this.repositoriesPerModule = ImmutableMap.copyOf(repositoriesPerModule);
+  }
+
+  @CheckForNull
+  public FileData fileData(String moduleKeyWithBranch, String path) {
+    SingleProjectRepository repository = repositoriesPerModule.get(moduleKeyWithBranch);
+    return repository == null ? null : repository.fileData(path);
+  }
+
+}
index bed6c1c2c34f740021efeae0ee32d0eba4bf0acd..e4cce89c392357e7d72aba83c2716fb4a912583b 100644 (file)
  */
 package org.sonar.scanner.repository;
 
-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;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
 
 @Immutable
-public class ProjectRepositories {
-  private final ImmutableTable<String, String, FileData> fileDataByModuleAndPath;
+public abstract class ProjectRepositories {
   private final Date lastAnalysisDate;
   private final boolean exists;
 
-  public ProjectRepositories() {
-    this.exists = false;
-    this.fileDataByModuleAndPath = new ImmutableTable.Builder<String, String, FileData>().build();
-    this.lastAnalysisDate = null;
-  }
-
-  public ProjectRepositories(Table<String, String, FileData> fileDataByModuleAndPath,
-                             @Nullable Date lastAnalysisDate) {
-    this.fileDataByModuleAndPath = ImmutableTable.copyOf(fileDataByModuleAndPath);
+  public ProjectRepositories(@Nullable Date lastAnalysisDate, boolean exists) {
     this.lastAnalysisDate = lastAnalysisDate;
-    this.exists = true;
+    this.exists = exists;
   }
 
   public boolean exists() {
     return exists;
   }
 
-  public Map<String, FileData> fileDataByPath(String moduleKey) {
-    return fileDataByModuleAndPath.row(moduleKey);
-  }
-
-  public Table<String, String, FileData> fileDataByModuleAndPath() {
-    return fileDataByModuleAndPath;
-  }
-
   @CheckForNull
-  public FileData fileData(String projectKeyWithBranch, String path) {
-    return fileDataByModuleAndPath.get(projectKeyWithBranch, path);
+  public FileData fileData(String moduleKeyWithBranch, DefaultInputFile inputFile) {
+    if (this instanceof SingleProjectRepository) {
+      return ((SingleProjectRepository) this).fileData(inputFile.getProjectRelativePath());
+    } else {
+      return ((MultiModuleProjectRepository) this).fileData(moduleKeyWithBranch, inputFile.getModuleRelativePath());
+    }
   }
 
   @CheckForNull
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/SingleProjectRepository.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/SingleProjectRepository.java
new file mode 100644 (file)
index 0000000..0d9637c
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.repository;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Date;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+@Immutable
+public class SingleProjectRepository extends ProjectRepositories {
+
+  private final ImmutableMap<String, FileData> fileDataByPath;
+
+  public SingleProjectRepository() {
+    super(null, false);
+    this.fileDataByPath = ImmutableMap.<String, FileData>builder().build();
+  }
+
+  public SingleProjectRepository(Map<String, FileData> fileDataByPath,
+    @Nullable Date lastAnalysisDate) {
+    super(lastAnalysisDate, true);
+    this.fileDataByPath = ImmutableMap.copyOf(fileDataByPath);
+  }
+
+  @CheckForNull
+  public FileData fileData(String path) {
+    return fileDataByPath.get(path);
+  }
+}
index 2554bf6b2fedf208d6fe460b8e44d931deeef7b7..dedb65043c77e92be17347146d1920106212f5e9 100644 (file)
@@ -34,7 +34,6 @@ import static org.sonar.api.batch.fs.InputFile.Status.SAME;
 
 @Immutable
 public class StatusDetection {
-
   private final ProjectRepositories projectRepositories;
   private final ScmChangedFiles scmChangedFiles;
 
@@ -43,8 +42,8 @@ public class StatusDetection {
     this.scmChangedFiles = scmChangedFiles;
   }
 
-  InputFile.Status status(String projectKeyWithBranch, DefaultInputFile inputFile, String hash) {
-    FileData fileDataPerPath = projectRepositories.fileData(projectKeyWithBranch, inputFile.relativePath());
+  InputFile.Status status(String moduleKeyWithBranch, DefaultInputFile inputFile, String hash) {
+    FileData fileDataPerPath = projectRepositories.fileData(moduleKeyWithBranch, inputFile);
     if (fileDataPerPath == null) {
       return checkChanged(ADDED, inputFile);
     }
@@ -64,8 +63,8 @@ public class StatusDetection {
    * @return null if it was not possible to get the status without calculating metadata
    */
   @CheckForNull
-  public InputFile.Status getStatusWithoutMetadata(String projectKeyWithBranch, DefaultInputFile inputFile) {
-    FileData fileDataPerPath = projectRepositories.fileData(projectKeyWithBranch, inputFile.relativePath());
+  public InputFile.Status getStatusWithoutMetadata(String moduleKeyWithBranch, DefaultInputFile inputFile) {
+    FileData fileDataPerPath = projectRepositories.fileData(moduleKeyWithBranch, inputFile);
     if (fileDataPerPath == null) {
       return checkChanged(ADDED, inputFile);
     }
index 78184f1dfb11b109c801251320e35e28eadfad7e..424f3ee1459715b28eae6b9462243748b058f6b6 100644 (file)
@@ -27,8 +27,8 @@ import org.sonar.api.batch.InstantiationStrategy;
 import org.sonar.api.batch.ScannerSide;
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.batch.fs.InputFile.Status;
-import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.batch.fs.internal.AbstractProjectOrModule;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.batch.scm.ScmProvider;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
@@ -57,7 +57,7 @@ public final class ScmPublisher {
   private final BranchConfiguration branchConfiguration;
 
   public ScmPublisher(AbstractProjectOrModule inputModule, ScmConfiguration configuration, ProjectRepositories projectRepositories,
-                      ModuleInputComponentStore componentStore, DefaultModuleFileSystem fs, ReportPublisher reportPublisher, BranchConfiguration branchConfiguration) {
+    ModuleInputComponentStore componentStore, DefaultModuleFileSystem fs, ReportPublisher reportPublisher, BranchConfiguration branchConfiguration) {
     this.inputModule = inputModule;
     this.configuration = configuration;
     this.projectRepositories = projectRepositories;
@@ -108,7 +108,7 @@ public final class ScmPublisher {
         addIfNotEmpty(filesToBlame, f);
       } else if (!branchConfiguration.isShortOrPullRequest()) {
         // File status is SAME so that mean fileData exists
-        FileData fileData = projectRepositories.fileData(inputModule.definition().getKeyWithBranch(), inputFile.getModuleRelativePath());
+        FileData fileData = projectRepositories.fileData(inputModule.definition().getKeyWithBranch(), inputFile);
         if (StringUtils.isEmpty(fileData.revision())) {
           addIfNotEmpty(filesToBlame, f);
         } else {
index eee733bc972185ce3adda594f834502afe8edd3b..75111308e826fa899417557f779ea8c6cce353ad 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.scanner.mediumtest;
 
 import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Table;
 import java.io.File;
 import java.io.FileInputStream;
@@ -62,6 +63,7 @@ import org.sonar.scanner.repository.ProjectRepositories;
 import org.sonar.scanner.repository.ProjectRepositoriesLoader;
 import org.sonar.scanner.repository.QualityProfileLoader;
 import org.sonar.scanner.repository.ServerIssuesLoader;
+import org.sonar.scanner.repository.SingleProjectRepository;
 import org.sonar.scanner.repository.settings.SettingsLoader;
 import org.sonar.scanner.rule.ActiveRulesLoader;
 import org.sonar.scanner.rule.LoadedActiveRule;
@@ -199,22 +201,22 @@ public class ScannerMediumTester extends ExternalResource {
   }
 
   public ScannerMediumTester addActiveRule(String repositoryKey, String ruleKey, @Nullable String templateRuleKey, String name, @Nullable String severity,
-    @Nullable String internalKey, @Nullable String languag) {
+    @Nullable String internalKey, @Nullable String language) {
     LoadedActiveRule r = new LoadedActiveRule();
 
     r.setInternalKey(internalKey);
     r.setRuleKey(RuleKey.of(repositoryKey, ruleKey));
     r.setName(name);
     r.setTemplateRuleKey(templateRuleKey);
-    r.setLanguage(languag);
+    r.setLanguage(language);
     r.setSeverity(severity);
 
     activeRules.addActiveRule(r);
     return this;
   }
 
-  public ScannerMediumTester addFileData(String moduleKey, String path, FileData fileData) {
-    projectRefProvider.addFileData(moduleKey, path, fileData);
+  public ScannerMediumTester addFileData(String path, FileData fileData) {
+    projectRefProvider.addFileData(path, fileData);
     return this;
   }
 
@@ -368,17 +370,16 @@ public class ScannerMediumTester extends ExternalResource {
   }
 
   private static class FakeProjectRepositoriesLoader implements ProjectRepositoriesLoader {
-
-    private Table<String, String, FileData> fileDataTable = HashBasedTable.create();
+    private Map<String, FileData> fileDataMap = Maps.newHashMap();
     private Date lastAnalysisDate;
 
     @Override
     public ProjectRepositories load(String projectKey, boolean isIssuesMode, @Nullable String branchBase) {
-      return new ProjectRepositories(fileDataTable, lastAnalysisDate);
+      return new SingleProjectRepository(fileDataMap, lastAnalysisDate);
     }
 
-    public FakeProjectRepositoriesLoader addFileData(String moduleKey, String path, FileData fileData) {
-      fileDataTable.put(moduleKey, path, fileData);
+    public FakeProjectRepositoriesLoader addFileData(String path, FileData fileData) {
+      fileDataMap.put(path, fileData);
       return this;
     }
 
index 62899cf17f12f9fb126f37d0cb6da354a3f73c08..7c52194f8dec7f2238765cf5949d2b98244acb6b 100644 (file)
@@ -66,7 +66,7 @@ public class BranchMediumTest {
     String md5sum = new FileMetadata()
       .readMetadata(Files.newInputStream(filepath), StandardCharsets.UTF_8, FILE_PATH)
       .hash();
-    tester.addFileData(PROJECT_KEY, FILE_PATH, new FileData(md5sum, "1.1"));
+    tester.addFileData(FILE_PATH, new FileData(md5sum, "1.1"));
   }
 
   @Test
index 4f68f3040fa45bc3b4b48f8fd00a4853b3cf24a9..1c31872a3a802593147f6115009ab0810091e13b 100644 (file)
@@ -47,9 +47,9 @@ import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.fs.internal.FileMetadata;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.scanner.issue.tracking.TrackedIssue;
+import org.sonar.scanner.mediumtest.AnalysisResult;
 import org.sonar.scanner.mediumtest.ScannerMediumTester;
 import org.sonar.scanner.mediumtest.ScannerMediumTester.AnalysisBuilder;
-import org.sonar.scanner.mediumtest.AnalysisResult;
 import org.sonar.scanner.protocol.Constants.Severity;
 import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue;
 import org.sonar.scanner.repository.FileData;
@@ -104,7 +104,7 @@ public class ScanOnlyChangedTest {
 
     tester
       // this will cause the file to have status==SAME
-      .addFileData(projectKey, filePath, new FileData(md5sum, null))
+      .addFileData(filePath, new FileData(md5sum, null))
       .setPreviousAnalysisDate(new Date())
       // Existing issue that is copied
       .mockServerIssue(ServerIssue.newBuilder().setKey("xyz")
index 94f6030b3e6eb319cac9a7d5d2b9e2197de1383e..42d7f836bf59ff12492824dc55ebecd45facca0a 100644 (file)
@@ -69,10 +69,10 @@ public class ScmMediumTest {
     .addRules(new XooRulesDefinition())
     // active a rule just to be sure that xoo files are published
     .addActiveRule("xoo", "xoo:OneIssuePerFile", null, "One Issue Per File", null, null, null)
-    .addFileData("com.foo.project", CHANGED_CONTENT_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), null))
-    .addFileData("com.foo.project", SAME_CONTENT_NO_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), null))
-    .addFileData("com.foo.project", SAME_CONTENT_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), "1.1"))
-    .addFileData("com.foo.project", NO_BLAME_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), "1.1"));
+    .addFileData(CHANGED_CONTENT_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), null))
+    .addFileData(SAME_CONTENT_NO_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), null))
+    .addFileData(SAME_CONTENT_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), "1.1"))
+    .addFileData(NO_BLAME_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), "1.1"));
 
   @Test
   public void testScmMeasure() throws IOException, URISyntaxException {
index c4749d87a2baf157a12079fe3bbf3e947ee6f8f2..fff3d8a78301da850f514f9d69245542a15a647f 100644 (file)
@@ -28,6 +28,7 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.utils.MessageException;
 import org.sonar.scanner.WsTestUtil;
 import org.sonar.scanner.bootstrap.ScannerWsClient;
@@ -124,9 +125,11 @@ public class DefaultProjectRepositoriesLoaderTest {
     InputStream is = getTestResource("project.protobuf");
     WsTestUtil.mockStream(wsClient, "/batch/project.protobuf?key=org.sonarsource.github%3Asonar-github-plugin&issues_mode=true", is);
 
+    DefaultInputFile file = mock(DefaultInputFile.class);
+    when(file.getModuleRelativePath()).thenReturn("src/test/java/org/sonar/plugins/github/PullRequestIssuePostJobTest.java");
+
     ProjectRepositories proj = loader.load("org.sonarsource.github:sonar-github-plugin", true, null);
-    FileData fd = proj.fileData("org.sonarsource.github:sonar-github-plugin",
-      "src/test/java/org/sonar/plugins/github/PullRequestIssuePostJobTest.java");
+    FileData fd = proj.fileData("org.sonarsource.github:sonar-github-plugin", file);
 
     assertThat(fd.revision()).isEqualTo("27bf2c54633d05c5df402bbe09471fe43bd9e2e5");
     assertThat(fd.hash()).isEqualTo("edb6b3b9ab92d8dc53ba90ab86cd422e");
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/MultiModuleProjectRepositoryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/MultiModuleProjectRepositoryTest.java
new file mode 100644 (file)
index 0000000..4bc505e
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.repository;
+
+import java.util.Date;
+import java.util.Map;
+import org.assertj.core.util.Maps;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertNotNull;
+
+public class MultiModuleProjectRepositoryTest {
+
+  private MultiModuleProjectRepository repository;
+
+  @Before
+  public void setUp() {
+    Date lastAnalysisDate = new Date();
+    SingleProjectRepository repository1 = new SingleProjectRepository(Maps.newHashMap("/Abc.java", new FileData("123", "456")), lastAnalysisDate);
+    SingleProjectRepository repository2 = new SingleProjectRepository(Maps.newHashMap("/Def.java", new FileData("567", "321")), lastAnalysisDate);
+    Map<String, SingleProjectRepository> moduleRepositories = Maps.newHashMap("module1", repository1);
+    moduleRepositories.put("module2", repository2);
+
+    repository = new MultiModuleProjectRepository(moduleRepositories, lastAnalysisDate);
+  }
+
+  @Test
+  public void test_file_data_when_module_and_file_exist() {
+    FileData fileData = repository.fileData("module2", "/Def.java");
+
+    assertNotNull(fileData);
+    assertThat(fileData.hash()).isEqualTo("567");
+    assertThat(fileData.revision()).isEqualTo("321");
+  }
+
+  @Test
+  public void test_file_data_when_module_does_not_exist() {
+    FileData fileData = repository.fileData("unknown", "/Def.java");
+
+    assertThat(fileData).isNull();
+  }
+}
index 515c1b3873e46fc14abece40a6b1f0147373ce5b..9af998d5ebded1aff1dd717e1d0ba1c9e53f63eb 100644 (file)
@@ -19,9 +19,9 @@
  */
 package org.sonar.scanner.repository;
 
-import com.google.common.collect.HashBasedTable;
-import com.google.common.collect.Table;
+import com.google.common.collect.Maps;
 import java.util.Date;
+import java.util.Map;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
@@ -55,9 +55,9 @@ public class ProjectRepositoriesProviderTest {
   public void setUp() {
     MockitoAnnotations.initMocks(this);
 
-    Table<String, String, FileData> t2 = HashBasedTable.create();
+    Map<String, FileData> fileMap = Maps.newHashMap();
 
-    project = new ProjectRepositories(t2, new Date());
+    project = new SingleProjectRepository(fileMap, new Date());
     provider = new ProjectRepositoriesProvider();
 
     when(props.getKeyWithBranch()).thenReturn("key");
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/SingleProjectRepositoryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/SingleProjectRepositoryTest.java
new file mode 100644 (file)
index 0000000..8a92b4c
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.repository;
+
+import java.util.Date;
+import org.assertj.core.util.Maps;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertNotNull;
+
+public class SingleProjectRepositoryTest {
+
+  private SingleProjectRepository repository;
+
+  @Before
+  public void setUp() {
+    Date lastAnalysisDate = new Date();
+    repository = new SingleProjectRepository(Maps.newHashMap("/Abc.java", new FileData("123", "456")), lastAnalysisDate);
+  }
+
+  @Test
+  public void test_file_data_when_file_exists() {
+    FileData fileData = repository.fileData("/Abc.java");
+
+    assertNotNull(fileData);
+    assertThat(fileData.hash()).isEqualTo("123");
+    assertThat(fileData.revision()).isEqualTo("456");
+  }
+
+  @Test
+  public void test_file_data_when_file_does_not_exist() {
+    FileData fileData = repository.fileData("/Def.java");
+
+    assertThat(fileData).isNull();
+  }
+}
index 09dba259df93ec04ce23a0580d38e3d6adec9f1b..c73f303965b8dd74addae15b95dd7ad9b624da33 100644 (file)
  */
 package org.sonar.scanner.scan.filesystem;
 
-import com.google.common.collect.HashBasedTable;
-import com.google.common.collect.Table;
 import java.nio.file.Paths;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 import org.junit.Test;
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
 import org.sonar.scanner.repository.FileData;
-import org.sonar.scanner.repository.ProjectRepositories;
+import org.sonar.scanner.repository.SingleProjectRepository;
 import org.sonar.scanner.scm.ScmChangedFiles;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -40,7 +40,7 @@ import static org.mockito.Mockito.when;
 public class StatusDetectionTest {
   @Test
   public void detect_status() {
-    ProjectRepositories ref = new ProjectRepositories(createTable(), null);
+    SingleProjectRepository ref = new SingleProjectRepository(createFileDataPerPathMap(), null);
     ScmChangedFiles changedFiles = new ScmChangedFiles(null);
     StatusDetection statusDetection = new StatusDetection(ref, changedFiles);
 
@@ -51,7 +51,7 @@ public class StatusDetectionTest {
 
   @Test
   public void detect_status_branches_exclude() {
-    ProjectRepositories ref = new ProjectRepositories(createTable(), null);
+    SingleProjectRepository ref = new SingleProjectRepository(createFileDataPerPathMap(), null);
     ScmChangedFiles changedFiles = new ScmChangedFiles(Collections.emptyList());
     StatusDetection statusDetection = new StatusDetection(ref, changedFiles);
 
@@ -65,34 +65,34 @@ public class StatusDetectionTest {
   @Test
   public void detect_status_without_metadata() {
     DefaultInputFile mockedFile = mock(DefaultInputFile.class);
-    when(mockedFile.relativePath()).thenReturn("module/src/Foo.java");
+    when(mockedFile.getProjectRelativePath()).thenReturn("module/src/Foo.java");
     when(mockedFile.path()).thenReturn(Paths.get("module", "src", "Foo.java"));
 
-    ProjectRepositories ref = new ProjectRepositories(createTable(), null);
+    SingleProjectRepository ref = new SingleProjectRepository(createFileDataPerPathMap(), null);
     ScmChangedFiles changedFiles = new ScmChangedFiles(Collections.singletonList(Paths.get("module", "src", "Foo.java")));
     StatusDetection statusDetection = new StatusDetection(ref, changedFiles);
 
     assertThat(statusDetection.getStatusWithoutMetadata("foo", mockedFile)).isEqualTo(InputFile.Status.ADDED);
 
     verify(mockedFile).path();
-    verify(mockedFile).relativePath();
+    verify(mockedFile).getProjectRelativePath();
     verifyNoMoreInteractions(mockedFile);
   }
 
   @Test
   public void detect_status_branches_confirm() {
-    ProjectRepositories ref = new ProjectRepositories(createTable(), null);
+    SingleProjectRepository ref = new SingleProjectRepository(createFileDataPerPathMap(), null);
     ScmChangedFiles changedFiles = new ScmChangedFiles(Collections.singletonList(Paths.get("module", "src", "Foo.java")));
     StatusDetection statusDetection = new StatusDetection(ref, changedFiles);
 
     assertThat(statusDetection.status("foo", createFile("src/Foo.java"), "XXXXX")).isEqualTo(InputFile.Status.CHANGED);
   }
 
-  private static Table<String, String, FileData> createTable() {
-    Table<String, String, FileData> t = HashBasedTable.create();
+  private static Map<String, FileData> createFileDataPerPathMap() {
+    Map<String, FileData> t = new HashMap<>();
 
-    t.put("foo", "src/Foo.java", new FileData("ABCDE", "12345789"));
-    t.put("foo", "src/Bar.java", new FileData("FGHIJ", "123456789"));
+    t.put("src/Foo.java", new FileData("ABCDE", "12345789"));
+    t.put("src/Bar.java", new FileData("FGHIJ", "123456789"));
 
     return t;
   }
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/MultiModuleProjectRepository.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/MultiModuleProjectRepository.java
new file mode 100644 (file)
index 0000000..7fbb9bb
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.protocol.input;
+
+import com.google.common.collect.Maps;
+import java.util.Map;
+import java.util.Optional;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public class MultiModuleProjectRepository extends ProjectRepositories {
+  private Map<String, SingleProjectRepository> repositoryPerModule = Maps.newHashMap();
+
+  public ProjectRepositories addFileDataToModule(String moduleKey, @Nullable String path, FileData fileData) {
+    if (path == null || (fileData.hash() == null && fileData.revision() == null)) {
+      return this;
+    }
+
+    SingleProjectRepository repository = repositoryPerModule.computeIfAbsent(moduleKey, k -> new SingleProjectRepository());
+    repository.addFileData(path, fileData);
+    return this;
+  }
+
+  public Map<String, SingleProjectRepository> repositoriesByModule() {
+    return repositoryPerModule;
+  }
+
+  @CheckForNull
+  public FileData fileData(String moduleKeyWithBranch, @Nullable String path) {
+    Optional<SingleProjectRepository> moduleRepository = Optional.ofNullable(repositoryPerModule.get(moduleKeyWithBranch));
+    return moduleRepository
+      .map(singleProjectRepository -> singleProjectRepository.fileDataByPath(path))
+      .orElse(null);
+  }
+}
index 9a557aa64cc87d194243c3a7f2303031a7ea604f..3737d98ab77968fe5e74d0bf2d76c181621f866a 100644 (file)
  */
 package org.sonar.scanner.protocol.input;
 
-import java.util.Collections;
 import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 
@@ -30,34 +27,11 @@ import javax.annotation.Nullable;
  * Container for all project data going from server to batch.
  * This is not an API since server and batch always share the same version.
  */
-public class ProjectRepositories {
+public abstract class ProjectRepositories {
 
   private long timestamp;
-  private Map<String, Map<String, FileData>> fileDataByModuleAndPath = new HashMap<>();
-  private Date lastAnalysisDate;
-
-  public Map<String, Map<String, FileData>> fileDataByModuleAndPath() {
-    return fileDataByModuleAndPath;
-  }
-
-  public Map<String, FileData> fileDataByPath(String moduleKey) {
-    return fileDataByModuleAndPath.containsKey(moduleKey) ? fileDataByModuleAndPath.get(moduleKey) : Collections.<String, FileData>emptyMap();
-  }
-
-  public ProjectRepositories addFileData(String moduleKey, @Nullable String path, FileData fileData) {
-    if (path == null || (fileData.hash() == null && fileData.revision() == null)) {
-      return this;
-    }
 
-    Map<String, FileData> existingFileDataByPath = fileDataByModuleAndPath.computeIfAbsent(moduleKey, k -> new HashMap<>());
-    existingFileDataByPath.put(path, fileData);
-    return this;
-  }
-
-  @CheckForNull
-  public FileData fileData(String projectKey, String path) {
-    return fileDataByPath(projectKey).get(path);
-  }
+  private Date lastAnalysisDate;
 
   public long timestamp() {
     return timestamp;
diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/SingleProjectRepository.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/SingleProjectRepository.java
new file mode 100644 (file)
index 0000000..37af28c
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.protocol.input;
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+public class SingleProjectRepository extends ProjectRepositories {
+  private Map<String, FileData> fileDataByPath = new HashMap<>();
+
+  public ProjectRepositories addFileData(@Nullable String path, FileData fileData) {
+    if (path == null || (fileData.hash() == null && fileData.revision() == null)) {
+      return this;
+    }
+
+    fileDataByPath.put(path, fileData);
+    return this;
+  }
+
+  public Map<String, FileData> fileData() {
+    return fileDataByPath;
+  }
+
+  public FileData fileDataByPath(@Nullable String path) {
+    return fileDataByPath.get(path);
+  }
+}
diff --git a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/MultiModuleProjectRepositoryTest.java b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/MultiModuleProjectRepositoryTest.java
new file mode 100644 (file)
index 0000000..61aea5c
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.protocol.input;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MultiModuleProjectRepositoryTest {
+
+  private MultiModuleProjectRepository repository;
+
+  @Before
+  public void setUp() {
+    repository = new MultiModuleProjectRepository();
+  }
+
+  @Test
+  public void add_file_data_to_nodule() {
+    FileData fileData1 = new FileData("123", "456");
+    FileData fileData2 = new FileData("153", "6432");
+    FileData fileData3 = new FileData("987", "6343");
+
+    repository.addFileDataToModule("Module1", "/Abc.java", fileData1);
+    repository.addFileDataToModule("Module1", "/Xyz.java", fileData2);
+    repository.addFileDataToModule("Module2", "/Def.java", fileData3);
+
+    assertThat(repository.repositoriesByModule()).hasSize(2);
+    assertThat(repository.fileData("Module1", "/Xyz.java")).isEqualTo(fileData2);
+    assertThat(repository.fileData("Module2", "/Def.java")).isEqualTo(fileData3);
+  }
+
+  @Test
+  public void add_file_does_not_add_the_file_without_path() {
+    FileData fileData = new FileData("123", "456");
+
+    repository.addFileDataToModule("module1", null, fileData);
+
+    assertThat(repository.repositoriesByModule()).hasSize(0);
+  }
+
+  @Test
+  public void add_file_does_not_add_the_file_without_revision_and_hash() {
+    FileData fileData = new FileData(null, null);
+
+    repository.addFileDataToModule("module2", "/Abc.java", fileData);
+
+    assertThat(repository.repositoriesByModule()).hasSize(0);
+  }
+}
diff --git a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/SingleProjectRepositoryTest.java b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/SingleProjectRepositoryTest.java
new file mode 100644 (file)
index 0000000..c16c0fc
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.protocol.input;
+
+import com.google.common.collect.Maps;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SingleProjectRepositoryTest {
+  private SingleProjectRepository repository;
+
+  @Before
+  public void setUp() {
+    repository = new SingleProjectRepository();
+  }
+
+  @Test
+  public void add_file_data() {
+    FileData fileData = new FileData("123", "456");
+
+    repository.addFileData("/Abc.java", fileData);
+
+    assertThat(repository.fileData()).hasSize(1);
+    assertThat(repository.fileData()).contains(Maps.immutableEntry("/Abc.java", fileData));
+    assertThat(repository.fileDataByPath("/Abc.java")).isEqualTo(fileData);
+  }
+
+  @Test
+  public void add_file_data_doesnt_add_the_file_without_path() {
+    FileData fileData = new FileData("123", "456");
+
+    repository.addFileData(null, fileData);
+
+    assertThat(repository.fileData()).hasSize(0);
+  }
+
+  @Test
+  public void add_file_data_doesnt_add_the_file_without_revision_and_hash() {
+    FileData fileData = new FileData(null, null);
+
+    repository.addFileData("/Abc.java", fileData);
+
+    assertThat(repository.fileData()).hasSize(0);
+  }
+}
index d600acadc325c049fe7aaa9339da11fc3fb1eb20..54b2a5dfba452016103443273c90e349f33859b9 100644 (file)
@@ -30,6 +30,7 @@ message WsProjectResponse {
   optional int64 timestamp = 1;
   map<string, FileDataByPath> fileDataByModuleAndPath = 3;
   optional int64 lastAnalysisDate = 4;
+  map<string, FileData> fileDataByPath = 5;
 
   message FileDataByPath {
     map<string, FileData> FileDataByPath = 1;