/* * SonarQube * Copyright (C) 2009-2025 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 com.google.common.collect.ImmutableMap; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.regex.Pattern; import java.util.stream.Stream; import javax.annotation.CheckForNull; import org.sonar.api.SonarEdition; import org.sonar.api.SonarRuntime; import org.sonar.api.batch.fs.InputComponent; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultFileSystem; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.fs.internal.DefaultInputModule; import org.sonar.api.batch.fs.internal.predicates.FileExtensionPredicate; import org.sonar.core.language.UnanalyzedLanguages; import org.sonar.scanner.scan.branch.BranchConfiguration; import static org.sonar.api.utils.Preconditions.checkNotNull; import static org.sonar.api.utils.Preconditions.checkState; /** * Store of all files and dirs. Inclusion and * exclusion patterns are already applied. */ public class InputComponentStore extends DefaultFileSystem.Cache { private static final Map FILE_PATTERN_BY_LANGUAGE = ImmutableMap.of( UnanalyzedLanguages.C, Pattern.compile(".*\\.c", Pattern.CASE_INSENSITIVE), UnanalyzedLanguages.CPP, Pattern.compile(".*\\.cpp|.*\\.cc|.*\\.cxx|.*\\.c\\+\\+", Pattern.CASE_INSENSITIVE)); private final SortedSet globalLanguagesCache = new TreeSet<>(); private final Map> languagesCache = new HashMap<>(); private final Map globalInputFileCache = new HashMap<>(); private final Map> inputFileByModuleCache = new LinkedHashMap<>(); private final Map inputModuleKeyByFileCache = new HashMap<>(); private final Map inputModuleCache = new HashMap<>(); private final Map inputComponents = new HashMap<>(); private final Map> filesByNameCache = new HashMap<>(); private final Map> filesByExtensionCache = new HashMap<>(); private final BranchConfiguration branchConfiguration; private final SonarRuntime sonarRuntime; private final Map notAnalysedFilesByLanguage = new HashMap<>(); public InputComponentStore(BranchConfiguration branchConfiguration, SonarRuntime sonarRuntime) { this.branchConfiguration = branchConfiguration; this.sonarRuntime = sonarRuntime; } public Collection all() { return inputComponents.values(); } private Stream allFilesToPublishStream() { return globalInputFileCache.values().stream() .map(f -> (DefaultInputFile) f) .filter(DefaultInputFile::isPublished); } public Iterable allFilesToPublish() { return allFilesToPublishStream()::iterator; } public Iterable allChangedFilesToPublish() { return allFilesToPublishStream() .filter(f -> !branchConfiguration.isPullRequest() || f.status() != InputFile.Status.SAME)::iterator; } @Override public Collection inputFiles() { return globalInputFileCache.values(); } public InputComponent getByKey(String key) { return inputComponents.get(key); } public Iterable filesByModule(String moduleKey) { return inputFileByModuleCache.getOrDefault(moduleKey, Collections.emptyMap()).values(); } public InputComponentStore put(String moduleKey, InputFile inputFile) { DefaultInputFile file = (DefaultInputFile) inputFile; updateNotAnalysedCAndCppFileCount(file); addToLanguageCache(moduleKey, file); inputFileByModuleCache.computeIfAbsent(moduleKey, x -> new HashMap<>()).put(file.getModuleRelativePath(), inputFile); inputModuleKeyByFileCache.put(inputFile, moduleKey); globalInputFileCache.put(file.getProjectRelativePath(), inputFile); inputComponents.put(inputFile.key(), inputFile); filesByNameCache.computeIfAbsent(inputFile.filename(), x -> new LinkedHashSet<>()).add(inputFile); filesByExtensionCache.computeIfAbsent(FileExtensionPredicate.getExtension(inputFile), x -> new LinkedHashSet<>()).add(inputFile); return this; } private void addToLanguageCache(String moduleKey, DefaultInputFile inputFile) { String language = inputFile.language(); if (language != null) { globalLanguagesCache.add(language); languagesCache.computeIfAbsent(moduleKey, k -> new TreeSet<>()).add(language); } } @CheckForNull public InputFile getFile(String moduleKey, String relativePath) { return inputFileByModuleCache.getOrDefault(moduleKey, Collections.emptyMap()) .get(relativePath); } @Override @CheckForNull public InputFile inputFile(String relativePath) { return globalInputFileCache.get(relativePath); } public DefaultInputModule findModule(DefaultInputFile file) { return Optional.ofNullable(inputModuleKeyByFileCache.get(file)).map(inputModuleCache::get) .orElseThrow(() -> new IllegalStateException("No modules for file '" + file.toString() + "'")); } public void put(DefaultInputModule inputModule) { String key = inputModule.key(); checkNotNull(inputModule); checkState(!inputComponents.containsKey(key), "Module '%s' already indexed", key); checkState(!inputModuleCache.containsKey(key), "Module '%s' already indexed", key); inputComponents.put(key, inputModule); inputModuleCache.put(key, inputModule); } @Override public Iterable getFilesByName(String filename) { return filesByNameCache.getOrDefault(filename, Collections.emptySet()); } @Override public Iterable getFilesByExtension(String extension) { return filesByExtensionCache.getOrDefault(extension, Collections.emptySet()); } @Override public SortedSet languages() { return globalLanguagesCache; } public SortedSet languages(String moduleKey) { return languagesCache.getOrDefault(moduleKey, Collections.emptySortedSet()); } public Collection allModules() { return inputModuleCache.values(); } @Override protected void doAdd(InputFile inputFile) { throw new UnsupportedOperationException(); } private void updateNotAnalysedCAndCppFileCount(DefaultInputFile inputFile) { if (!SonarEdition.COMMUNITY.equals(sonarRuntime.getEdition())) { return; } FILE_PATTERN_BY_LANGUAGE.forEach((language, filePattern) -> { if (filePattern.matcher(inputFile.filename()).matches()) { notAnalysedFilesByLanguage.put(language.toString(), notAnalysedFilesByLanguage.getOrDefault(language.toString(), 0) + 1); } }); } public Map getNotAnalysedFilesByLanguage() { return ImmutableMap.copyOf(notAnalysedFilesByLanguage); } }