You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FileIndexer.java 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. /*
  2. * SonarQube, open source software quality management tool.
  3. * Copyright (C) 2008-2014 SonarSource
  4. * mailto:contact AT sonarsource DOT com
  5. *
  6. * SonarQube is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * SonarQube is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.batch.scan.filesystem;
  21. import com.google.common.collect.Sets;
  22. import org.apache.commons.io.FileUtils;
  23. import org.apache.commons.io.filefilter.FileFilterUtils;
  24. import org.apache.commons.io.filefilter.HiddenFileFilter;
  25. import org.apache.commons.io.filefilter.IOFileFilter;
  26. import org.slf4j.Logger;
  27. import org.slf4j.LoggerFactory;
  28. import org.sonar.api.BatchComponent;
  29. import org.sonar.api.batch.bootstrap.ProjectDefinition;
  30. import org.sonar.api.batch.fs.InputFile;
  31. import org.sonar.api.batch.fs.InputFileFilter;
  32. import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile;
  33. import org.sonar.api.utils.MessageException;
  34. import java.io.File;
  35. import java.util.ArrayList;
  36. import java.util.Collection;
  37. import java.util.HashSet;
  38. import java.util.List;
  39. import java.util.Set;
  40. import java.util.concurrent.Callable;
  41. import java.util.concurrent.ExecutionException;
  42. import java.util.concurrent.ExecutorService;
  43. import java.util.concurrent.Executors;
  44. import java.util.concurrent.Future;
  45. /**
  46. * Index input files into {@link InputFileCache}.
  47. */
  48. public class FileIndexer implements BatchComponent {
  49. private static final Logger LOG = LoggerFactory.getLogger(FileIndexer.class);
  50. private static final IOFileFilter DIR_FILTER = FileFilterUtils.and(HiddenFileFilter.VISIBLE, FileFilterUtils.notFileFilter(FileFilterUtils.prefixFileFilter(".")));
  51. private static final IOFileFilter FILE_FILTER = HiddenFileFilter.VISIBLE;
  52. private final List<InputFileFilter> filters;
  53. private final InputFileCache fileCache;
  54. private final boolean isAggregator;
  55. private final ExclusionFilters exclusionFilters;
  56. private final InputFileBuilderFactory inputFileBuilderFactory;
  57. public FileIndexer(List<InputFileFilter> filters, ExclusionFilters exclusionFilters, InputFileBuilderFactory inputFileBuilderFactory,
  58. InputFileCache cache, ProjectDefinition def) {
  59. this(filters, exclusionFilters, inputFileBuilderFactory, cache, !def.getSubProjects().isEmpty());
  60. }
  61. private FileIndexer(List<InputFileFilter> filters, ExclusionFilters exclusionFilters, InputFileBuilderFactory inputFileBuilderFactory,
  62. InputFileCache cache, boolean isAggregator) {
  63. this.filters = filters;
  64. this.exclusionFilters = exclusionFilters;
  65. this.inputFileBuilderFactory = inputFileBuilderFactory;
  66. this.fileCache = cache;
  67. this.isAggregator = isAggregator;
  68. }
  69. void index(DefaultModuleFileSystem fileSystem) {
  70. if (isAggregator) {
  71. // No indexing for an aggregator module
  72. return;
  73. }
  74. LOG.info("Index files");
  75. exclusionFilters.prepare();
  76. Progress progress = new Progress(fileCache.byModule(fileSystem.moduleKey()));
  77. InputFileBuilder inputFileBuilder = inputFileBuilderFactory.create(fileSystem);
  78. indexFiles(fileSystem, progress, inputFileBuilder, fileSystem.sources(), InputFile.Type.MAIN);
  79. indexFiles(fileSystem, progress, inputFileBuilder, fileSystem.tests(), InputFile.Type.TEST);
  80. indexAllConcurrently(progress);
  81. // Populate FS in a synchronous way because PersistIt Exchange is not concurrent
  82. for (InputFile indexed : progress.indexed) {
  83. fileSystem.add(indexed);
  84. }
  85. // Remove files that have been removed since previous indexation
  86. for (InputFile removed : progress.removed) {
  87. fileCache.remove(fileSystem.moduleKey(), removed);
  88. }
  89. LOG.info(String.format("%d files indexed", progress.count()));
  90. }
  91. private void indexAllConcurrently(Progress progress) {
  92. ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1);
  93. try {
  94. List<Future<Void>> all = executor.invokeAll(progress.indexingTasks);
  95. for (Future<Void> future : all) {
  96. future.get();
  97. }
  98. } catch (InterruptedException e) {
  99. throw new IllegalStateException("FileIndexer was interrupted", e);
  100. } catch (ExecutionException e) {
  101. Throwable cause = e.getCause();
  102. if (cause instanceof RuntimeException) {
  103. throw (RuntimeException) cause;
  104. } else {
  105. throw new IllegalStateException("Error during file indexing", e);
  106. }
  107. }
  108. executor.shutdown();
  109. }
  110. private void indexFiles(DefaultModuleFileSystem fileSystem, Progress progress, InputFileBuilder inputFileBuilder, List<File> sources, InputFile.Type type) {
  111. for (File dirOrFile : sources) {
  112. if (dirOrFile.isDirectory()) {
  113. indexDirectory(inputFileBuilder, fileSystem, progress, dirOrFile, type);
  114. } else {
  115. indexFile(inputFileBuilder, fileSystem, progress, dirOrFile, type);
  116. }
  117. }
  118. }
  119. private void indexDirectory(InputFileBuilder inputFileBuilder, DefaultModuleFileSystem fileSystem, Progress status, File dirToIndex, InputFile.Type type) {
  120. Collection<File> files = FileUtils.listFiles(dirToIndex, FILE_FILTER, DIR_FILTER);
  121. for (File file : files) {
  122. indexFile(inputFileBuilder, fileSystem, status, file, type);
  123. }
  124. }
  125. private void indexFile(InputFileBuilder inputFileBuilder, DefaultModuleFileSystem fileSystem, Progress progress, File sourceFile, InputFile.Type type) {
  126. DeprecatedDefaultInputFile inputFile = inputFileBuilder.create(sourceFile);
  127. if (inputFile != null && exclusionFilters.accept(inputFile, type)) {
  128. indexFile(inputFileBuilder, fileSystem, progress, inputFile, type);
  129. }
  130. }
  131. private void indexFile(final InputFileBuilder inputFileBuilder, final DefaultModuleFileSystem fs,
  132. final Progress status, final DeprecatedDefaultInputFile inputFile, final InputFile.Type type) {
  133. Callable<Void> task = new Callable<Void>() {
  134. @Override
  135. public Void call() throws Exception {
  136. InputFile completedFile = inputFileBuilder.complete(inputFile, type);
  137. if (completedFile != null && accept(completedFile)) {
  138. status.markAsIndexed(inputFile);
  139. }
  140. return null;
  141. }
  142. };
  143. status.planForIndexing(task);
  144. }
  145. private boolean accept(InputFile inputFile) {
  146. // InputFileFilter extensions
  147. for (InputFileFilter filter : filters) {
  148. if (!filter.accept(inputFile)) {
  149. return false;
  150. }
  151. }
  152. return true;
  153. }
  154. private static class Progress {
  155. private final Set<InputFile> removed;
  156. private final Set<InputFile> indexed;
  157. private final List<Callable<Void>> indexingTasks;
  158. Progress(Iterable<InputFile> removed) {
  159. this.removed = Sets.newHashSet(removed);
  160. this.indexed = new HashSet<InputFile>();
  161. this.indexingTasks = new ArrayList<Callable<Void>>();
  162. }
  163. void planForIndexing(Callable<Void> indexingTask) {
  164. this.indexingTasks.add(indexingTask);
  165. }
  166. synchronized void markAsIndexed(InputFile inputFile) {
  167. if (indexed.contains(inputFile)) {
  168. throw MessageException.of("File " + inputFile + " can't be indexed twice. Please check that inclusion/exclusion patterns produce "
  169. + "disjoint sets for main and test files");
  170. }
  171. removed.remove(inputFile);
  172. indexed.add(inputFile);
  173. }
  174. int count() {
  175. return indexed.size();
  176. }
  177. }
  178. }