diff options
author | Duarte Meneses <duarte.meneses@sonarsource.com> | 2019-06-04 09:24:58 -0500 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-07-12 20:21:13 +0200 |
commit | e3a0108ef0dd26689dc58198e85ba8655c77fb1f (patch) | |
tree | d2cedb4bfd8cb6ac737420b8f45713475871ee89 /sonar-scanner-engine/src/main/java/org/sonar/scanner/fs | |
parent | 0f63c1e2bdf0c9fad3ca66ea64ef197bb16e6258 (diff) | |
download | sonarqube-e3a0108ef0dd26689dc58198e85ba8655c77fb1f.tar.gz sonarqube-e3a0108ef0dd26689dc58198e85ba8655c77fb1f.zip |
Extract implementation from plugin API - Scanner FS
Diffstat (limited to 'sonar-scanner-engine/src/main/java/org/sonar/scanner/fs')
42 files changed, 3828 insertions, 0 deletions
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/AbstractProjectOrModule.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/AbstractProjectOrModule.java new file mode 100644 index 00000000000..9fc2d130f4a --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/AbstractProjectOrModule.java @@ -0,0 +1,161 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.CheckForNull; +import javax.annotation.concurrent.Immutable; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.SystemUtils; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +@Immutable +public abstract class AbstractProjectOrModule extends DefaultInputComponent { + private static final Logger LOGGER = Loggers.get(AbstractProjectOrModule.class); + private final Path baseDir; + private final Path workDir; + private final String name; + private final String originalName; + private final String description; + private final String keyWithBranch; + private final String branch; + private final Map<String, String> properties; + + private final String key; + private final ProjectDefinition definition; + private final Charset encoding; + + public AbstractProjectOrModule(ProjectDefinition definition, int scannerComponentId) { + super(scannerComponentId); + this.baseDir = initBaseDir(definition); + this.workDir = initWorkingDir(definition); + this.name = definition.getName(); + this.originalName = definition.getOriginalName(); + this.description = definition.getDescription(); + this.keyWithBranch = definition.getKeyWithBranch(); + this.branch = definition.getBranch(); + this.properties = Collections.unmodifiableMap(new HashMap<>(definition.properties())); + + this.definition = definition; + this.key = definition.getKey(); + this.encoding = initEncoding(definition); + } + + private static Charset initEncoding(ProjectDefinition module) { + String encodingStr = module.properties().get(CoreProperties.ENCODING_PROPERTY); + Charset result; + if (StringUtils.isNotEmpty(encodingStr)) { + result = Charset.forName(StringUtils.trim(encodingStr)); + } else { + result = Charset.defaultCharset(); + } + return result; + } + + private static Path initBaseDir(ProjectDefinition module) { + Path result; + try { + result = module.getBaseDir().toPath().toRealPath(LinkOption.NOFOLLOW_LINKS); + } catch (IOException e) { + throw new IllegalStateException("Unable to resolve module baseDir", e); + } + return result; + } + + private static Path initWorkingDir(ProjectDefinition module) { + File workingDirAsFile = module.getWorkDir(); + Path workingDir = workingDirAsFile.getAbsoluteFile().toPath().normalize(); + if (SystemUtils.IS_OS_WINDOWS) { + try { + Files.createDirectories(workingDir); + Files.setAttribute(workingDir, "dos:hidden", true, LinkOption.NOFOLLOW_LINKS); + } catch (IOException e) { + LOGGER.warn("Failed to set working directory hidden: {}", e.getMessage()); + } + } + return workingDir; + } + + /** + * Module key without branch + */ + @Override + public String key() { + return key; + } + + @Override + public boolean isFile() { + return false; + } + + public ProjectDefinition definition() { + return definition; + } + + public Path getBaseDir() { + return baseDir; + } + + public Path getWorkDir() { + return workDir; + } + + public String getKeyWithBranch() { + return keyWithBranch; + } + + @CheckForNull + public String getBranch() { + return branch; + } + + public Map<String, String> properties() { + return properties; + } + + @CheckForNull + public String getOriginalName() { + return originalName; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Charset getEncoding() { + return encoding; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultFileSystem.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultFileSystem.java new file mode 100644 index 00000000000..1c1729634e1 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultFileSystem.java @@ -0,0 +1,246 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.stream.StreamSupport; +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.FilePredicates; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputDir; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.scan.filesystem.PathResolver; +import org.sonar.api.utils.PathUtils; +import org.sonar.scanner.fs.predicates.DefaultFilePredicates; +import org.sonar.scanner.fs.predicates.FileExtensionPredicate; +import org.sonar.scanner.fs.predicates.OptimizedFilePredicateAdapter; + +/** + * @since 4.2 + */ +public class DefaultFileSystem implements FileSystem { + + private final Cache cache; + private final Path baseDir; + private Path workDir; + private Charset encoding; + protected final FilePredicates predicates; + + /** + * Only for testing + */ + public DefaultFileSystem(Path baseDir) { + this(baseDir, new MapCache()); + } + + /** + * Only for testing + */ + public DefaultFileSystem(File baseDir) { + this(baseDir.toPath(), new MapCache()); + } + + protected DefaultFileSystem(Path baseDir, Cache cache) { + this.baseDir = baseDir; + this.cache = cache; + this.predicates = new DefaultFilePredicates(this.baseDir); + } + + public Path baseDirPath() { + return baseDir; + } + + @Override + public File baseDir() { + return baseDir.toFile(); + } + + public DefaultFileSystem setEncoding(Charset e) { + this.encoding = e; + return this; + } + + @Override + public Charset encoding() { + return encoding; + } + + public DefaultFileSystem setWorkDir(Path d) { + this.workDir = d; + return this; + } + + @Override + public File workDir() { + return workDir.toFile(); + } + + @Override + public InputFile inputFile(FilePredicate predicate) { + Iterable<InputFile> files = inputFiles(predicate); + Iterator<InputFile> iterator = files.iterator(); + if (!iterator.hasNext()) { + return null; + } + InputFile first = iterator.next(); + if (!iterator.hasNext()) { + return first; + } + + StringBuilder sb = new StringBuilder(); + sb.append("expected one element but was: <" + first); + for (int i = 0; i < 4 && iterator.hasNext(); i++) { + sb.append(", " + iterator.next()); + } + if (iterator.hasNext()) { + sb.append(", ..."); + } + sb.append('>'); + + throw new IllegalArgumentException(sb.toString()); + + } + + public Iterable<InputFile> inputFiles() { + return inputFiles(predicates.all()); + } + + @Override + public Iterable<InputFile> inputFiles(FilePredicate predicate) { + return OptimizedFilePredicateAdapter.create(predicate).get(cache); + } + + @Override + public boolean hasFiles(FilePredicate predicate) { + return inputFiles(predicate).iterator().hasNext(); + } + + @Override + public Iterable<File> files(FilePredicate predicate) { + return () -> StreamSupport.stream(inputFiles(predicate).spliterator(), false) + .map(InputFile::file) + .iterator(); + } + + @Override + public InputDir inputDir(File dir) { + String relativePath = PathUtils.sanitize(new PathResolver().relativePath(baseDir.toFile(), dir)); + if (relativePath == null) { + return null; + } + // Issues on InputDir are moved to the project, so we just return a fake InputDir for backward compatibility + return new DefaultInputDir("unused", relativePath).setModuleBaseDir(baseDir); + } + + public DefaultFileSystem add(InputFile inputFile) { + cache.add(inputFile); + return this; + } + + @Override + public SortedSet<String> languages() { + return cache.languages(); + } + + @Override + public FilePredicates predicates() { + return predicates; + } + + public abstract static class Cache implements Index { + + protected abstract void doAdd(InputFile inputFile); + + final void add(InputFile inputFile) { + doAdd(inputFile); + } + + protected abstract SortedSet<String> languages(); + } + + /** + * Used only for testing + */ + private static class MapCache extends Cache { + private final Map<String, InputFile> fileMap = new HashMap<>(); + private final Map<String, Set<InputFile>> filesByNameCache = new HashMap<>(); + private final Map<String, Set<InputFile>> filesByExtensionCache = new HashMap<>(); + private SortedSet<String> languages = new TreeSet<>(); + + @Override + public Iterable<InputFile> inputFiles() { + return new ArrayList<>(fileMap.values()); + } + + @Override + public InputFile inputFile(String relativePath) { + return fileMap.get(relativePath); + } + + @Override + public Iterable<InputFile> getFilesByName(String filename) { + return filesByNameCache.get(filename); + } + + @Override + public Iterable<InputFile> getFilesByExtension(String extension) { + return filesByExtensionCache.get(extension); + } + + @Override + protected void doAdd(InputFile inputFile) { + if (inputFile.language() != null) { + languages.add(inputFile.language()); + } + fileMap.put(inputFile.relativePath(), inputFile); + filesByNameCache.computeIfAbsent(inputFile.filename(), x -> new HashSet<>()).add(inputFile); + filesByExtensionCache.computeIfAbsent(FileExtensionPredicate.getExtension(inputFile), x -> new HashSet<>()).add(inputFile); + } + + @Override + protected SortedSet<String> languages() { + return languages; + } + } + + @Override + public File resolvePath(String path) { + File file = new File(path); + if (!file.isAbsolute()) { + try { + file = new File(baseDir(), path).getCanonicalFile(); + } catch (IOException e) { + throw new IllegalArgumentException("Unable to resolve path '" + path + "'", e); + } + } + return file; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultIndexedFile.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultIndexedFile.java new file mode 100644 index 00000000000..a3d060217e0 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultIndexedFile.java @@ -0,0 +1,158 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +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; + +/** + * @since 6.3 + */ +@Immutable +public class DefaultIndexedFile extends DefaultInputComponent implements IndexedFile { + private final String projectRelativePath; + private final String moduleRelativePath; + private final String projectKey; + private final String language; + private final Type type; + private final Path absolutePath; + private final SensorStrategy sensorStrategy; + + /** + * Testing purposes only! + */ + public DefaultIndexedFile(String projectKey, Path baseDir, String relativePath, @Nullable String language) { + this(baseDir.resolve(relativePath), projectKey, relativePath, relativePath, Type.MAIN, language, TestInputFileBuilder.nextBatchId(), + new SensorStrategy()); + } + + public DefaultIndexedFile(Path absolutePath, String projectKey, String projectRelativePath, String moduleRelativePath, Type type, @Nullable String language, int batchId, + SensorStrategy sensorStrategy) { + super(batchId); + this.projectKey = projectKey; + this.projectRelativePath = PathUtils.sanitize(projectRelativePath); + this.moduleRelativePath = PathUtils.sanitize(moduleRelativePath); + this.type = type; + this.language = language; + this.sensorStrategy = sensorStrategy; + this.absolutePath = absolutePath; + } + + @Override + public String relativePath() { + return sensorStrategy.isGlobal() ? projectRelativePath : moduleRelativePath; + } + + public String getModuleRelativePath() { + return moduleRelativePath; + } + + public String getProjectRelativePath() { + return projectRelativePath; + } + + @Override + public String absolutePath() { + return PathUtils.sanitize(path().toString()); + } + + @Override + public File file() { + return path().toFile(); + } + + @Override + public Path path() { + return absolutePath; + } + + @Override + public InputStream inputStream() throws IOException { + return Files.newInputStream(path()); + } + + @CheckForNull + @Override + public String language() { + return language; + } + + @Override + public Type type() { + return type; + } + + /** + * Component key (without branch). + */ + @Override + public String key() { + return new StringBuilder().append(projectKey).append(":").append(projectRelativePath).toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof DefaultIndexedFile)) { + return false; + } + + DefaultIndexedFile that = (DefaultIndexedFile) o; + return projectRelativePath.equals(that.projectRelativePath); + } + + @Override + public int hashCode() { + return projectRelativePath.hashCode(); + } + + @Override + public String toString() { + return projectRelativePath; + } + + @Override + public boolean isFile() { + return true; + } + + @Override + public String filename() { + return path().getFileName().toString(); + } + + @Override + public URI uri() { + return path().toUri(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultInputComponent.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultInputComponent.java new file mode 100644 index 00000000000..11601e0265f --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultInputComponent.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs; + +import java.util.HashSet; +import java.util.Set; +import org.sonar.api.batch.fs.InputComponent; +import org.sonar.api.batch.measure.Metric; + +/** + * @since 5.2 + */ +public abstract class DefaultInputComponent implements InputComponent { + private int id; + private Set<String> storedMetricKeys = new HashSet<>(); + + public DefaultInputComponent(int scannerId) { + this.id = scannerId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || this.getClass() != o.getClass()) { + return false; + } + + DefaultInputComponent that = (DefaultInputComponent) o; + return key().equals(that.key()); + } + + public int scannerId() { + return id; + } + + @Override + public int hashCode() { + return key().hashCode(); + } + + @Override + public String toString() { + return "[key=" + key() + "]"; + } + + public void setHasMeasureFor(Metric metric) { + storedMetricKeys.add(metric.key()); + } + + public boolean hasMeasureFor(Metric metric) { + return storedMetricKeys.contains(metric.key()); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultInputDir.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultInputDir.java new file mode 100644 index 00000000000..13eb77be1c4 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultInputDir.java @@ -0,0 +1,122 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs; + +import java.io.File; +import java.net.URI; +import java.nio.file.Path; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.batch.fs.InputDir; +import org.sonar.api.utils.PathUtils; + +/** + * @since 4.5 + */ +public class DefaultInputDir extends DefaultInputComponent implements InputDir { + + private final String relativePath; + private final String moduleKey; + private Path moduleBaseDir; + + public DefaultInputDir(String moduleKey, String relativePath) { + super(-1); + this.moduleKey = moduleKey; + this.relativePath = PathUtils.sanitize(relativePath); + } + + @Override + public String relativePath() { + return relativePath; + } + + @Override + public String absolutePath() { + return PathUtils.sanitize(path().toString()); + } + + @Override + public File file() { + return path().toFile(); + } + + @Override + public Path path() { + if (moduleBaseDir == null) { + throw new IllegalStateException("Can not return the java.nio.file.Path because module baseDir is not set (see method setModuleBaseDir(java.io.File))"); + } + return moduleBaseDir.resolve(relativePath); + } + + public String moduleKey() { + return moduleKey; + } + + @Override + public String key() { + StringBuilder sb = new StringBuilder().append(moduleKey).append(":"); + if (StringUtils.isEmpty(relativePath)) { + sb.append("/"); + } else { + sb.append(relativePath); + } + return sb.toString(); + } + + /** + * For testing purpose. Will be automatically set when dir is added to {@link DefaultFileSystem} + */ + public DefaultInputDir setModuleBaseDir(Path moduleBaseDir) { + this.moduleBaseDir = moduleBaseDir.normalize(); + return this; + } + + @Override + public boolean isFile() { + return false; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || this.getClass() != o.getClass()) { + return false; + } + + DefaultInputDir that = (DefaultInputDir) o; + return moduleKey.equals(that.moduleKey) && relativePath.equals(that.relativePath); + } + + @Override + public int hashCode() { + return moduleKey.hashCode() + relativePath.hashCode() * 13; + } + + @Override + public String toString() { + return "[moduleKey=" + moduleKey + ", relative=" + relativePath + ", basedir=" + moduleBaseDir + "]"; + } + + @Override + public URI uri() { + return path().toUri(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultInputFile.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultInputFile.java new file mode 100644 index 00000000000..f29f11db64a --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultInputFile.java @@ -0,0 +1,440 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collection; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.apache.commons.io.ByteOrderMark; +import org.apache.commons.io.input.BOMInputStream; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.TextPointer; +import org.sonar.api.batch.fs.TextRange; +import org.sonar.api.batch.fs.internal.Metadata; + +import static org.sonar.api.utils.Preconditions.checkArgument; +import static org.sonar.api.utils.Preconditions.checkState; + +/** + * @since 4.2 + * To create {@link InputFile} in tests, use {@link TestInputFileBuilder}. + */ +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 boolean published; + private boolean excludedForCoverage; + private boolean excludedForDuplication; + private boolean ignoreAllIssues; + // Lazy init to save memory + private BitSet noSonarLines; + private Status status; + private Charset charset; + private Metadata metadata; + private Collection<int[]> ignoreIssuesOnlineRanges; + private BitSet executableLines; + + public DefaultInputFile(DefaultIndexedFile indexedFile, Consumer<DefaultInputFile> metadataGenerator) { + this(indexedFile, metadataGenerator, null); + } + + // For testing + public DefaultInputFile(DefaultIndexedFile indexedFile, Consumer<DefaultInputFile> metadataGenerator, @Nullable String contents) { + super(indexedFile.scannerId()); + this.indexedFile = indexedFile; + this.metadataGenerator = metadataGenerator; + this.metadata = null; + this.published = false; + this.excludedForCoverage = false; + this.contents = contents; + } + + public void checkMetadata() { + if (metadata == null) { + metadataGenerator.accept(this); + } + } + + @Override + public InputStream inputStream() throws IOException { + return contents != null ? new ByteArrayInputStream(contents.getBytes(charset())) + : new BOMInputStream(Files.newInputStream(path()), + ByteOrderMark.UTF_8, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_32LE, ByteOrderMark.UTF_32BE); + } + + @Override + public String contents() throws IOException { + if (contents != null) { + return contents; + } else { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + try (InputStream inputStream = inputStream()) { + byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + int length; + while ((length = inputStream.read(buffer)) != -1) { + result.write(buffer, 0, length); + } + } + return result.toString(charset().name()); + } + } + + public DefaultInputFile setPublished(boolean published) { + this.published = published; + return this; + } + + public boolean isPublished() { + return published; + } + + public DefaultInputFile setExcludedForCoverage(boolean excludedForCoverage) { + this.excludedForCoverage = excludedForCoverage; + return this; + } + + public boolean isExcludedForCoverage() { + return excludedForCoverage; + } + + public DefaultInputFile setExcludedForDuplication(boolean excludedForDuplication) { + this.excludedForDuplication = excludedForDuplication; + return this; + } + + public boolean isExcludedForDuplication() { + return excludedForDuplication; + } + + /** + * @deprecated since 6.6 + */ + @Deprecated + @Override + public String relativePath() { + return indexedFile.relativePath(); + } + + public String getModuleRelativePath() { + return indexedFile.getModuleRelativePath(); + } + + public String getProjectRelativePath() { + return indexedFile.getProjectRelativePath(); + } + + @Override + public String absolutePath() { + return indexedFile.absolutePath(); + } + + @Override + public File file() { + return indexedFile.file(); + } + + @Override + public Path path() { + return indexedFile.path(); + } + + @CheckForNull + @Override + public String language() { + return indexedFile.language(); + } + + @Override + public Type type() { + return indexedFile.type(); + } + + /** + * Component key (without branch). + */ + @Override + public String key() { + return indexedFile.key(); + } + + @Override + public int hashCode() { + return indexedFile.hashCode(); + } + + @Override + public String toString() { + return indexedFile.toString(); + } + + /** + * {@link #setStatus(Status)} + */ + @Override + public Status status() { + checkMetadata(); + return status; + } + + @Override + public int lines() { + checkMetadata(); + return metadata.lines(); + } + + @Override + public boolean isEmpty() { + checkMetadata(); + return metadata.isEmpty(); + } + + @Override + public Charset charset() { + checkMetadata(); + return charset; + } + + public int lastValidOffset() { + checkMetadata(); + return metadata.lastValidOffset(); + } + + /** + * Digest hash of the file. + */ + public String hash() { + checkMetadata(); + return metadata.hash(); + } + + public int nonBlankLines() { + checkMetadata(); + return metadata.nonBlankLines(); + } + + public int[] originalLineStartOffsets() { + checkMetadata(); + checkState(metadata.originalLineStartOffsets() != null, "InputFile is not properly initialized."); + checkState(metadata.originalLineStartOffsets().length == metadata.lines(), + "InputFile is not properly initialized. 'originalLineStartOffsets' property length should be equal to 'lines'"); + return metadata.originalLineStartOffsets(); + } + + public int[] originalLineEndOffsets() { + checkMetadata(); + checkState(metadata.originalLineEndOffsets() != null, "InputFile is not properly initialized."); + checkState(metadata.originalLineEndOffsets().length == metadata.lines(), + "InputFile is not properly initialized. 'originalLineEndOffsets' property length should be equal to 'lines'"); + return metadata.originalLineEndOffsets(); + } + + @Override + public TextPointer newPointer(int line, int lineOffset) { + checkMetadata(); + DefaultTextPointer textPointer = new DefaultTextPointer(line, lineOffset); + checkValid(textPointer, "pointer"); + return textPointer; + } + + @Override + public TextRange newRange(TextPointer start, TextPointer end) { + checkMetadata(); + checkValid(start, "start pointer"); + checkValid(end, "end pointer"); + return newRangeValidPointers(start, end, false); + } + + @Override + public TextRange newRange(int startLine, int startLineOffset, int endLine, int endLineOffset) { + checkMetadata(); + TextPointer start = newPointer(startLine, startLineOffset); + TextPointer end = newPointer(endLine, endLineOffset); + return newRangeValidPointers(start, end, false); + } + + @Override + public TextRange selectLine(int line) { + checkMetadata(); + TextPointer startPointer = newPointer(line, 0); + TextPointer endPointer = newPointer(line, lineLength(line)); + return newRangeValidPointers(startPointer, endPointer, true); + } + + public void validate(TextRange range) { + checkMetadata(); + checkValid(range.start(), "start pointer"); + checkValid(range.end(), "end pointer"); + } + + /** + * Create Range from global offsets. Used for backward compatibility with older API. + */ + public TextRange newRange(int startOffset, int endOffset) { + checkMetadata(); + return newRangeValidPointers(newPointer(startOffset), newPointer(endOffset), false); + } + + public TextPointer newPointer(int globalOffset) { + checkMetadata(); + checkArgument(globalOffset >= 0, "%s is not a valid offset for a file", globalOffset); + checkArgument(globalOffset <= lastValidOffset(), "%s is not a valid offset for file %s. Max offset is %s", globalOffset, this, lastValidOffset()); + int line = findLine(globalOffset); + int startLineOffset = originalLineStartOffsets()[line - 1]; + // In case the global offset is between \r and \n, move the pointer to a valid location + return new DefaultTextPointer(line, Math.min(globalOffset, originalLineEndOffsets()[line - 1]) - startLineOffset); + } + + public DefaultInputFile setStatus(Status status) { + this.status = status; + return this; + } + + public DefaultInputFile setCharset(Charset charset) { + this.charset = charset; + return this; + } + + private void checkValid(TextPointer pointer, String owner) { + checkArgument(pointer.line() >= 1, "%s is not a valid line for a file", pointer.line()); + checkArgument(pointer.line() <= this.metadata.lines(), "%s is not a valid line for %s. File %s has %s line(s)", pointer.line(), owner, this, metadata.lines()); + checkArgument(pointer.lineOffset() >= 0, "%s is not a valid line offset for a file", pointer.lineOffset()); + int lineLength = lineLength(pointer.line()); + checkArgument(pointer.lineOffset() <= lineLength, + "%s is not a valid line offset for %s. File %s has %s character(s) at line %s", pointer.lineOffset(), owner, this, lineLength, pointer.line()); + } + + private int lineLength(int line) { + return originalLineEndOffsets()[line - 1] - originalLineStartOffsets()[line - 1]; + } + + private static TextRange newRangeValidPointers(TextPointer start, TextPointer end, boolean acceptEmptyRange) { + checkArgument(acceptEmptyRange ? (start.compareTo(end) <= 0) : (start.compareTo(end) < 0), + "Start pointer %s should be before end pointer %s", start, end); + return new DefaultTextRange(start, end); + } + + private int findLine(int globalOffset) { + return Math.abs(Arrays.binarySearch(originalLineStartOffsets(), globalOffset) + 1); + } + + public DefaultInputFile setMetadata(Metadata metadata) { + this.metadata = metadata; + return this; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + + if (this.getClass() != obj.getClass()) { + return false; + } + + DefaultInputFile that = (DefaultInputFile) obj; + return this.getProjectRelativePath().equals(that.getProjectRelativePath()); + } + + @Override + public boolean isFile() { + return true; + } + + @Override + public String filename() { + return indexedFile.filename(); + } + + @Override + public URI uri() { + return indexedFile.uri(); + } + + public void noSonarAt(Set<Integer> noSonarLines) { + if (this.noSonarLines == null) { + this.noSonarLines = new BitSet(lines()); + } + noSonarLines.forEach(l -> this.noSonarLines.set(l - 1)); + } + + public boolean hasNoSonarAt(int line) { + if (this.noSonarLines == null) { + return false; + } + return this.noSonarLines.get(line - 1); + } + + public boolean isIgnoreAllIssues() { + return ignoreAllIssues; + } + + public void setIgnoreAllIssues(boolean ignoreAllIssues) { + this.ignoreAllIssues = ignoreAllIssues; + } + + public void addIgnoreIssuesOnLineRanges(Collection<int[]> lineRanges) { + if (this.ignoreIssuesOnlineRanges == null) { + this.ignoreIssuesOnlineRanges = new ArrayList<>(); + } + this.ignoreIssuesOnlineRanges.addAll(lineRanges); + } + + public boolean isIgnoreAllIssuesOnLine(@Nullable Integer line) { + if (line == null || ignoreIssuesOnlineRanges == null) { + return false; + } + return ignoreIssuesOnlineRanges.stream().anyMatch(r -> r[0] <= line && line <= r[1]); + } + + public void setExecutableLines(Set<Integer> executableLines) { + checkState(this.executableLines == null, "Executable lines have already been saved for file: {}", this.toString()); + this.executableLines = new BitSet(lines()); + executableLines.forEach(l -> this.executableLines.set(l - 1)); + } + + public Optional<Set<Integer>> getExecutableLines() { + if (this.executableLines == null) { + return Optional.empty(); + } + return Optional.of(this.executableLines.stream().map(i -> i + 1).boxed().collect(Collectors.toSet())); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultInputModule.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultInputModule.java new file mode 100644 index 00000000000..a729bff7856 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultInputModule.java @@ -0,0 +1,81 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import javax.annotation.CheckForNull; +import javax.annotation.concurrent.Immutable; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.fs.InputModule; +import org.sonar.api.scan.filesystem.PathResolver; + +import static org.sonar.api.config.internal.MultivalueProperty.parseAsCsv; + +@Immutable +public class DefaultInputModule extends AbstractProjectOrModule implements InputModule { + + private final List<Path> sourceDirsOrFiles; + private final List<Path> testDirsOrFiles; + + /** + * For testing only! + */ + public DefaultInputModule(ProjectDefinition definition) { + this(definition, 0); + } + + public DefaultInputModule(ProjectDefinition definition, int scannerComponentId) { + super(definition, scannerComponentId); + + this.sourceDirsOrFiles = initSources(definition, ProjectDefinition.SOURCES_PROPERTY); + this.testDirsOrFiles = initSources(definition, ProjectDefinition.TESTS_PROPERTY); + } + + @CheckForNull + private List<Path> initSources(ProjectDefinition module, String propertyKey) { + if (!module.properties().containsKey(propertyKey)) { + return null; + } + List<Path> result = new ArrayList<>(); + PathResolver pathResolver = new PathResolver(); + String srcPropValue = module.properties().get(propertyKey); + if (srcPropValue != null) { + for (String sourcePath : parseAsCsv(propertyKey, srcPropValue)) { + File dirOrFile = pathResolver.relativeFile(getBaseDir().toFile(), sourcePath); + if (dirOrFile.exists()) { + result.add(dirOrFile.toPath()); + } + } + } + return result; + } + + public Optional<List<Path>> getSourceDirsOrFiles() { + return Optional.ofNullable(sourceDirsOrFiles); + } + + public Optional<List<Path>> getTestDirsOrFiles() { + return Optional.ofNullable(testDirsOrFiles); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultInputProject.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultInputProject.java new file mode 100644 index 00000000000..9dcd0254b00 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultInputProject.java @@ -0,0 +1,39 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs; + +import javax.annotation.concurrent.Immutable; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.scanner.fs.InputProject; + +@Immutable +public class DefaultInputProject extends AbstractProjectOrModule implements InputProject { + + /** + * For testing only! + */ + public DefaultInputProject(ProjectDefinition definition) { + super(definition, 0); + } + + public DefaultInputProject(ProjectDefinition definition, int scannerComponentId) { + super(definition, scannerComponentId); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultTextPointer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultTextPointer.java new file mode 100644 index 00000000000..5cfc84fc712 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultTextPointer.java @@ -0,0 +1,74 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs; + +import org.sonar.api.batch.fs.TextPointer; + +/** + * @since 5.2 + */ +public class DefaultTextPointer implements TextPointer { + + private final int line; + private final int lineOffset; + + public DefaultTextPointer(int line, int lineOffset) { + this.line = line; + this.lineOffset = lineOffset; + } + + @Override + public int line() { + return line; + } + + @Override + public int lineOffset() { + return lineOffset; + } + + @Override + public String toString() { + return "[line=" + line + ", lineOffset=" + lineOffset + "]"; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof DefaultTextPointer)) { + return false; + } + DefaultTextPointer other = (DefaultTextPointer) obj; + return other.line == this.line && other.lineOffset == this.lineOffset; + } + + @Override + public int hashCode() { + return 37 * this.line + lineOffset; + } + + @Override + public int compareTo(TextPointer o) { + if (this.line == o.line()) { + return Integer.compare(this.lineOffset, o.lineOffset()); + } + return Integer.compare(this.line, o.line()); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultTextRange.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultTextRange.java new file mode 100644 index 00000000000..f83c330e7ea --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/DefaultTextRange.java @@ -0,0 +1,74 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs; + +import org.sonar.api.batch.fs.TextPointer; +import org.sonar.api.batch.fs.TextRange; + +/** + * @since 5.2 + */ +public class DefaultTextRange implements TextRange { + + private final TextPointer start; + private final TextPointer end; + + public DefaultTextRange(TextPointer start, TextPointer end) { + this.start = start; + this.end = end; + } + + @Override + public TextPointer start() { + return start; + } + + @Override + public TextPointer end() { + return end; + } + + @Override + public boolean overlap(TextRange another) { + // [A,B] and [C,D] + // B > C && D > A + return this.end.compareTo(another.start()) > 0 && another.end().compareTo(this.start) > 0; + } + + @Override + public String toString() { + return "Range[from " + start + " to " + end + "]"; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof DefaultTextRange)) { + return false; + } + DefaultTextRange other = (DefaultTextRange) obj; + return start.equals(other.start) && end.equals(other.end); + } + + @Override + public int hashCode() { + return start.hashCode() * 17 + end.hashCode(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/FileMetadata.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/FileMetadata.java new file mode 100644 index 00000000000..1b984168d51 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/FileMetadata.java @@ -0,0 +1,162 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.Metadata; +import org.sonar.scanner.fs.charhandler.CharHandler; +import org.sonar.scanner.fs.charhandler.FileHashComputer; +import org.sonar.scanner.fs.charhandler.LineCounter; +import org.sonar.scanner.fs.charhandler.LineHashComputer; +import org.sonar.scanner.fs.charhandler.LineOffsetCounter; + +/** + * Computes hash of files. Ends of Lines are ignored, so files with + * same content but different EOL encoding have the same hash. + */ +@Immutable +public class FileMetadata { + private static final char LINE_FEED = '\n'; + private static final char CARRIAGE_RETURN = '\r'; + + /** + * Compute hash of a file ignoring line ends differences. + * Maximum performance is needed. + */ + public Metadata readMetadata(InputStream stream, Charset encoding, String filePath, @Nullable CharHandler otherHandler) { + LineCounter lineCounter = new LineCounter(filePath, encoding); + FileHashComputer fileHashComputer = new FileHashComputer(filePath); + LineOffsetCounter lineOffsetCounter = new LineOffsetCounter(); + + if (otherHandler != null) { + CharHandler[] handlers = {lineCounter, fileHashComputer, lineOffsetCounter, otherHandler}; + readFile(stream, encoding, filePath, handlers); + } else { + CharHandler[] handlers = {lineCounter, fileHashComputer, lineOffsetCounter}; + readFile(stream, encoding, filePath, handlers); + } + return new Metadata(lineCounter.lines(), lineCounter.nonBlankLines(), fileHashComputer.getHash(), lineOffsetCounter.getOriginalLineStartOffsets(), + lineOffsetCounter.getOriginalLineEndOffsets(), + lineOffsetCounter.getLastValidOffset()); + } + + public Metadata readMetadata(InputStream stream, Charset encoding, String filePath) { + return readMetadata(stream, encoding, filePath, null); + } + + /** + * For testing purpose + */ + public Metadata readMetadata(Reader reader) { + LineCounter lineCounter = new LineCounter("fromString", StandardCharsets.UTF_16); + FileHashComputer fileHashComputer = new FileHashComputer("fromString"); + LineOffsetCounter lineOffsetCounter = new LineOffsetCounter(); + CharHandler[] handlers = {lineCounter, fileHashComputer, lineOffsetCounter}; + + try { + read(reader, handlers); + } catch (IOException e) { + throw new IllegalStateException("Should never occur", e); + } + return new Metadata(lineCounter.lines(), lineCounter.nonBlankLines(), fileHashComputer.getHash(), lineOffsetCounter.getOriginalLineStartOffsets(), + lineOffsetCounter.getOriginalLineEndOffsets(), + lineOffsetCounter.getLastValidOffset()); + } + + public static void readFile(InputStream stream, Charset encoding, String filePath, CharHandler[] handlers) { + try (Reader reader = new BufferedReader(new InputStreamReader(stream, encoding))) { + read(reader, handlers); + } catch (IOException e) { + throw new IllegalStateException(String.format("Fail to read file '%s' with encoding '%s'", filePath, encoding), e); + } + } + + private static void read(Reader reader, CharHandler[] handlers) throws IOException { + char c; + int i = reader.read(); + boolean afterCR = false; + while (i != -1) { + c = (char) i; + if (afterCR) { + for (CharHandler handler : handlers) { + if (c == CARRIAGE_RETURN) { + handler.newLine(); + handler.handleAll(c); + } else if (c == LINE_FEED) { + handler.handleAll(c); + handler.newLine(); + } else { + handler.newLine(); + handler.handleIgnoreEoL(c); + handler.handleAll(c); + } + } + afterCR = c == CARRIAGE_RETURN; + } else if (c == LINE_FEED) { + for (CharHandler handler : handlers) { + handler.handleAll(c); + handler.newLine(); + } + } else if (c == CARRIAGE_RETURN) { + afterCR = true; + for (CharHandler handler : handlers) { + handler.handleAll(c); + } + } else { + for (CharHandler handler : handlers) { + handler.handleIgnoreEoL(c); + handler.handleAll(c); + } + } + i = reader.read(); + } + for (CharHandler handler : handlers) { + if (afterCR) { + handler.newLine(); + } + handler.eof(); + } + } + + @FunctionalInterface + public interface LineHashConsumer { + void consume(int lineIdx, @Nullable byte[] hash); + } + + /** + * Compute a MD5 hash of each line of the file after removing of all blank chars + */ + public static void computeLineHashesForIssueTracking(InputFile f, LineHashConsumer consumer) { + try { + readFile(f.inputStream(), f.charset(), f.absolutePath(), new CharHandler[] {new LineHashComputer(consumer, f.file())}); + } catch (IOException e) { + throw new IllegalStateException("Failed to compute line hashes for " + f.absolutePath(), e); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/InputModuleHierarchy.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/InputModuleHierarchy.java new file mode 100644 index 00000000000..9048824f032 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/InputModuleHierarchy.java @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs; + +import java.util.Collection; +import javax.annotation.CheckForNull; +import javax.annotation.concurrent.Immutable; + +@Immutable +public interface InputModuleHierarchy { + DefaultInputModule root(); + + boolean isRoot(DefaultInputModule module); + + Collection<DefaultInputModule> children(DefaultInputModule module); + + @CheckForNull + DefaultInputModule parent(DefaultInputModule module); + + @CheckForNull + String relativePath(DefaultInputModule module); + + @CheckForNull + String relativePathToRoot(DefaultInputModule module); +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/PathPattern.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/PathPattern.java new file mode 100644 index 00000000000..5d5947d9927 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/PathPattern.java @@ -0,0 +1,136 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs; + +import java.nio.file.Path; +import javax.annotation.concurrent.ThreadSafe; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.utils.PathUtils; +import org.sonar.api.utils.WildcardPattern; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +@ThreadSafe +public abstract class PathPattern { + + private static final Logger LOG = Loggers.get(PathPattern.class); + + /** + * @deprecated since 6.6 + */ + @Deprecated + private static final String ABSOLUTE_PATH_PATTERN_PREFIX = "file:"; + final WildcardPattern pattern; + + PathPattern(String pattern) { + this.pattern = WildcardPattern.create(pattern); + } + + public abstract boolean match(Path absolutePath, Path relativePath); + + public abstract boolean match(Path absolutePath, Path relativePath, boolean caseSensitiveFileExtension); + + public static PathPattern create(String s) { + String trimmed = StringUtils.trim(s); + if (StringUtils.startsWithIgnoreCase(trimmed, ABSOLUTE_PATH_PATTERN_PREFIX)) { + LOG.warn("Using absolute path pattern is deprecated. Please use relative path instead of '" + trimmed + "'"); + return new AbsolutePathPattern(StringUtils.substring(trimmed, ABSOLUTE_PATH_PATTERN_PREFIX.length())); + } + return new RelativePathPattern(trimmed); + } + + public static PathPattern[] create(String[] s) { + PathPattern[] result = new PathPattern[s.length]; + for (int i = 0; i < s.length; i++) { + result[i] = create(s[i]); + } + return result; + } + + /** + * @deprecated since 6.6 + */ + @Deprecated + private static class AbsolutePathPattern extends PathPattern { + private AbsolutePathPattern(String pattern) { + super(pattern); + } + + @Override + public boolean match(Path absolutePath, Path relativePath) { + return match(absolutePath, relativePath, true); + } + + @Override + public boolean match(Path absolutePath, Path relativePath, boolean caseSensitiveFileExtension) { + String path = PathUtils.sanitize(absolutePath.toString()); + if (!caseSensitiveFileExtension) { + String extension = sanitizeExtension(FilenameUtils.getExtension(path)); + if (StringUtils.isNotBlank(extension)) { + path = StringUtils.removeEndIgnoreCase(path, extension); + path = path + extension; + } + } + return pattern.match(path); + } + + @Override + public String toString() { + return ABSOLUTE_PATH_PATTERN_PREFIX + pattern.toString(); + } + } + + /** + * Path relative to module basedir + */ + private static class RelativePathPattern extends PathPattern { + private RelativePathPattern(String pattern) { + super(pattern); + } + + @Override + public boolean match(Path absolutePath, Path relativePath) { + return match(absolutePath, relativePath, true); + } + + @Override + public boolean match(Path absolutePath, Path relativePath, boolean caseSensitiveFileExtension) { + String path = PathUtils.sanitize(relativePath.toString()); + if (!caseSensitiveFileExtension) { + String extension = sanitizeExtension(FilenameUtils.getExtension(path)); + if (StringUtils.isNotBlank(extension)) { + path = StringUtils.removeEndIgnoreCase(path, extension); + path = path + extension; + } + } + return path != null && pattern.match(path); + } + + @Override + public String toString() { + return pattern.toString(); + } + } + + static String sanitizeExtension(String suffix) { + return StringUtils.lowerCase(StringUtils.removeStart(suffix, ".")); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/SensorStrategy.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/SensorStrategy.java new file mode 100644 index 00000000000..244a01b052d --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/SensorStrategy.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs; + +import org.sonar.api.batch.fs.InputFile; + +/** + * A shared, mutable object in the project container. + * It's used during the execution of sensors to decide whether + * sensors should be executed once for the entire project, or per-module. + * It is also injected into each InputFile to change the behavior of {@link InputFile#relativePath()} + */ +public class SensorStrategy { + + private boolean global = true; + + public boolean isGlobal() { + return global; + } + + public void setGlobal(boolean global) { + this.global = global; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/TestInputFileBuilder.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/TestInputFileBuilder.java new file mode 100644 index 00000000000..5d6e8b9bc9c --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/TestInputFileBuilder.java @@ -0,0 +1,279 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.Paths; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.Metadata; +import org.sonar.api.utils.PathUtils; + +/** + * Intended to be used in unit tests that need to create {@link InputFile}s. + * An InputFile is unambiguously identified by a <b>module key</b> and a <b>relative path</b>, so these parameters are mandatory. + * <p> + * A module base directory is only needed to construct absolute paths. + * <p> + * Examples of usage of the constructors: + * + * <pre> + * InputFile file1 = TestInputFileBuilder.create("module1", "myfile.java").build(); + * InputFile file2 = TestInputFileBuilder.create("", fs.baseDir(), myfile).build(); + * </pre> + * <p> + * file1 will have the "module1" as both module key and module base directory. + * file2 has an empty string as module key, and a relative path which is the path from the filesystem base directory to myfile. + * + * @since 6.3 + */ +public class TestInputFileBuilder { + private static int batchId = 1; + + private final int id; + private final String relativePath; + private final String projectKey; + @CheckForNull + private Path projectBaseDir; + private Path moduleBaseDir; + private String language; + private InputFile.Type type = InputFile.Type.MAIN; + private InputFile.Status status; + private int lines = -1; + private Charset charset; + private String hash; + private int nonBlankLines; + private int[] originalLineStartOffsets = new int[0]; + private int[] originalLineEndOffsets = new int[0]; + private int lastValidOffset = -1; + private boolean publish = true; + private String contents; + + /** + * Create a InputFile identified by the given project key and relative path. + */ + public TestInputFileBuilder(String projectKey, String relativePath) { + this(projectKey, relativePath, batchId++); + } + + /** + * Create a InputFile with a given module key and module base directory. + * The relative path is generated comparing the file path to the module base directory. + * filePath must point to a file that is within the module base directory. + */ + public TestInputFileBuilder(String projectKey, File moduleBaseDir, File filePath) { + String relativePath = moduleBaseDir.toPath().relativize(filePath.toPath()).toString(); + this.projectKey = projectKey; + setModuleBaseDir(moduleBaseDir.toPath()); + this.relativePath = PathUtils.sanitize(relativePath); + this.id = batchId++; + } + + public TestInputFileBuilder(String projectKey, String relativePath, int id) { + this.projectKey = projectKey; + setModuleBaseDir(Paths.get(projectKey)); + this.relativePath = PathUtils.sanitize(relativePath); + this.id = id; + } + + public static TestInputFileBuilder create(String moduleKey, File moduleBaseDir, File filePath) { + return new TestInputFileBuilder(moduleKey, moduleBaseDir, filePath); + } + + public static TestInputFileBuilder create(String moduleKey, String relativePath) { + return new TestInputFileBuilder(moduleKey, relativePath); + } + + public static int nextBatchId() { + return batchId++; + } + + public TestInputFileBuilder setProjectBaseDir(Path projectBaseDir) { + this.projectBaseDir = normalize(projectBaseDir); + return this; + } + + public TestInputFileBuilder setModuleBaseDir(Path moduleBaseDir) { + this.moduleBaseDir = normalize(moduleBaseDir); + return this; + } + + private static Path normalize(Path path) { + try { + return path.normalize().toRealPath(LinkOption.NOFOLLOW_LINKS); + } catch (IOException e) { + return path.normalize(); + } + } + + public TestInputFileBuilder setLanguage(@Nullable String language) { + this.language = language; + return this; + } + + public TestInputFileBuilder setType(InputFile.Type type) { + this.type = type; + return this; + } + + public TestInputFileBuilder setStatus(InputFile.Status status) { + this.status = status; + return this; + } + + public TestInputFileBuilder setLines(int lines) { + this.lines = lines; + return this; + } + + public TestInputFileBuilder setCharset(Charset charset) { + this.charset = charset; + return this; + } + + public TestInputFileBuilder setHash(String hash) { + this.hash = hash; + return this; + } + + /** + * Set contents of the file and calculates metadata from it. + * The contents will be returned by {@link InputFile#contents()} and {@link InputFile#inputStream()} and can be + * inconsistent with the actual physical file pointed by {@link InputFile#path()}, {@link InputFile#absolutePath()}, etc. + */ + public TestInputFileBuilder setContents(String content) { + this.contents = content; + initMetadata(content); + return this; + } + + public TestInputFileBuilder setNonBlankLines(int nonBlankLines) { + this.nonBlankLines = nonBlankLines; + return this; + } + + public TestInputFileBuilder setLastValidOffset(int lastValidOffset) { + this.lastValidOffset = lastValidOffset; + return this; + } + + public TestInputFileBuilder setOriginalLineStartOffsets(int[] originalLineStartOffsets) { + this.originalLineStartOffsets = originalLineStartOffsets; + return this; + } + + public TestInputFileBuilder setOriginalLineEndOffsets(int[] originalLineEndOffsets) { + this.originalLineEndOffsets = originalLineEndOffsets; + return this; + } + + public TestInputFileBuilder setPublish(boolean publish) { + this.publish = publish; + return this; + } + + public TestInputFileBuilder setMetadata(Metadata metadata) { + this.setLines(metadata.lines()); + this.setLastValidOffset(metadata.lastValidOffset()); + this.setNonBlankLines(metadata.nonBlankLines()); + this.setHash(metadata.hash()); + this.setOriginalLineStartOffsets(metadata.originalLineStartOffsets()); + this.setOriginalLineEndOffsets(metadata.originalLineEndOffsets()); + return this; + } + + public TestInputFileBuilder initMetadata(String content) { + return setMetadata(new FileMetadata().readMetadata(new StringReader(content))); + } + + public DefaultInputFile build() { + Path absolutePath = moduleBaseDir.resolve(relativePath); + if (projectBaseDir == null) { + projectBaseDir = moduleBaseDir; + } + String projectRelativePath = projectBaseDir.relativize(absolutePath).toString(); + DefaultIndexedFile indexedFile = new DefaultIndexedFile(absolutePath, projectKey, projectRelativePath, relativePath, type, language, id, new SensorStrategy()); + DefaultInputFile inputFile = new DefaultInputFile(indexedFile, + f -> f.setMetadata(new Metadata(lines, nonBlankLines, hash, originalLineStartOffsets, originalLineEndOffsets, lastValidOffset)), + contents); + inputFile.setStatus(status); + inputFile.setCharset(charset); + inputFile.setPublished(publish); + return inputFile; + } + + public static DefaultInputModule newDefaultInputModule(String moduleKey, File baseDir) { + ProjectDefinition definition = ProjectDefinition.create() + .setKey(moduleKey) + .setBaseDir(baseDir) + .setWorkDir(new File(baseDir, ".sonar")); + return newDefaultInputModule(definition); + } + + public static DefaultInputModule newDefaultInputModule(ProjectDefinition projectDefinition) { + return new DefaultInputModule(projectDefinition, TestInputFileBuilder.nextBatchId()); + } + + public static DefaultInputModule newDefaultInputModule(AbstractProjectOrModule parent, String key) throws IOException { + Path basedir = parent.getBaseDir().resolve(key); + Files.createDirectory(basedir); + return newDefaultInputModule(key, basedir.toFile()); + } + + public static DefaultInputProject newDefaultInputProject(String projectKey, File baseDir) { + ProjectDefinition definition = ProjectDefinition.create() + .setKey(projectKey) + .setBaseDir(baseDir) + .setWorkDir(new File(baseDir, ".sonar")); + return newDefaultInputProject(definition); + } + + public static DefaultInputProject newDefaultInputProject(ProjectDefinition projectDefinition) { + return new DefaultInputProject(projectDefinition, TestInputFileBuilder.nextBatchId()); + } + + public static DefaultInputProject newDefaultInputProject(String key, Path baseDir) throws IOException { + Files.createDirectory(baseDir); + return newDefaultInputProject(key, baseDir.toFile()); + } + + public static DefaultInputDir newDefaultInputDir(AbstractProjectOrModule module, String relativePath) throws IOException { + Path basedir = module.getBaseDir().resolve(relativePath); + Files.createDirectory(basedir); + return new DefaultInputDir(module.key(), relativePath) + .setModuleBaseDir(module.getBaseDir()); + } + + public static DefaultInputFile newDefaultInputFile(Path projectBaseDir, AbstractProjectOrModule module, String relativePath) { + return new TestInputFileBuilder(module.key(), relativePath) + .setStatus(InputFile.Status.SAME) + .setProjectBaseDir(projectBaseDir) + .setModuleBaseDir(module.getBaseDir()) + .build(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/CharHandler.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/CharHandler.java new file mode 100644 index 00000000000..737f6c29071 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/CharHandler.java @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.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-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/FileHashComputer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/FileHashComputer.java new file mode 100644 index 00000000000..46229a5c70b --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/FileHashComputer.java @@ -0,0 +1,81 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.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 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); + } + } + + public String getHash() { + return Hex.encodeHexString(globalMd5Digest.digest()); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/IntArrayList.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/IntArrayList.java new file mode 100644 index 00000000000..12674145399 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/IntArrayList.java @@ -0,0 +1,117 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.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-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/LineCounter.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/LineCounter.java new file mode 100644 index 00000000000..7cac0f2b0a0 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/LineCounter.java @@ -0,0 +1,82 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.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-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/LineHashComputer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/LineHashComputer.java new file mode 100644 index 00000000000..5435499e505 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/LineHashComputer.java @@ -0,0 +1,81 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.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.scanner.fs.FileMetadata; + +public class LineHashComputer extends CharHandler { + private final MessageDigest lineMd5Digest = DigestUtils.getMd5Digest(); + private final CharsetEncoder encoder; + private final StringBuilder sb = new StringBuilder(); + private final FileMetadata.LineHashConsumer consumer; + private final File file; + private int line = 1; + + public LineHashComputer(FileMetadata.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-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/LineOffsetCounter.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/LineOffsetCounter.java new file mode 100644 index 00000000000..74b7f4867ba --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/LineOffsetCounter.java @@ -0,0 +1,74 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.charhandler; + +public class LineOffsetCounter extends CharHandler { + private long currentOriginalLineStartOffset = 0; + private long currentOriginalLineEndOffset = 0; + private final IntArrayList originalLineStartOffsets = new IntArrayList(); + private final IntArrayList originalLineEndOffsets = new IntArrayList(); + private long lastValidOffset = 0; + + public LineOffsetCounter() { + originalLineStartOffsets.add(0); + } + + @Override + public void handleAll(char c) { + currentOriginalLineStartOffset++; + } + + @Override + public void handleIgnoreEoL(char c) { + currentOriginalLineEndOffset++; + } + + @Override + public void newLine() { + if (currentOriginalLineStartOffset > Integer.MAX_VALUE) { + throw new IllegalStateException("File is too big: " + currentOriginalLineStartOffset); + } + originalLineStartOffsets.add((int) currentOriginalLineStartOffset); + originalLineEndOffsets.add((int) currentOriginalLineEndOffset); + currentOriginalLineEndOffset = currentOriginalLineStartOffset; + } + + @Override + public void eof() { + originalLineEndOffsets.add((int) currentOriginalLineEndOffset); + lastValidOffset = currentOriginalLineStartOffset; + } + + public int[] getOriginalLineStartOffsets() { + return originalLineStartOffsets.trimAndGet(); + } + + public int[] getOriginalLineEndOffsets() { + return originalLineEndOffsets.trimAndGet(); + } + + public int getLastValidOffset() { + if (lastValidOffset > Integer.MAX_VALUE) { + throw new IllegalStateException("File is too big: " + lastValidOffset); + } + return (int) lastValidOffset; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/package-info.java new file mode 100644 index 00000000000..4e5375fc17f --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/charhandler/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.scanner.fs.charhandler; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/package-info.java new file mode 100644 index 00000000000..e971997aa86 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.scanner.fs; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/AbsolutePathPredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/AbsolutePathPredicate.java new file mode 100644 index 00000000000..c55099a8884 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/AbsolutePathPredicate.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import java.io.File; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import org.sonar.api.batch.fs.FileSystem.Index; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.scan.filesystem.PathResolver; +import org.sonar.api.utils.PathUtils; + +/** + * @since 4.2 + */ +class AbsolutePathPredicate extends AbstractFilePredicate { + + private final String path; + private final Path baseDir; + + AbsolutePathPredicate(String path, Path baseDir) { + this.baseDir = baseDir; + this.path = PathUtils.sanitize(path); + } + + @Override + public boolean apply(InputFile f) { + return path.equals(f.absolutePath()); + } + + @Override + public Iterable<InputFile> get(Index index) { + String relative = PathUtils.sanitize(new PathResolver().relativePath(baseDir.toFile(), new File(path))); + if (relative == null) { + return Collections.emptyList(); + } + InputFile f = index.inputFile(relative); + return f != null ? Arrays.asList(f) : Collections.<InputFile>emptyList(); + } + + @Override + public int priority() { + return USE_INDEX; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/AbstractFilePredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/AbstractFilePredicate.java new file mode 100644 index 00000000000..4f6952611d7 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/AbstractFilePredicate.java @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import java.util.stream.StreamSupport; +import org.sonar.api.batch.fs.FileSystem.Index; +import org.sonar.api.batch.fs.InputFile; + +/** + * Partial implementation of {@link OptimizedFilePredicate}. + * @since 5.1 + */ +public abstract class AbstractFilePredicate implements OptimizedFilePredicate { + + protected static final int DEFAULT_PRIORITY = 10; + protected static final int USE_INDEX = 20; + + @Override + public Iterable<InputFile> filter(Iterable<InputFile> target) { + return () -> StreamSupport.stream(target.spliterator(), false) + .filter(this::apply) + .iterator(); + } + + @Override + public Iterable<InputFile> get(Index index) { + return filter(index.inputFiles()); + } + + @Override + public int priority() { + return DEFAULT_PRIORITY; + } + + @Override + public final int compareTo(OptimizedFilePredicate o) { + return o.priority() - priority(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/AndPredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/AndPredicate.java new file mode 100644 index 00000000000..024964256ba --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/AndPredicate.java @@ -0,0 +1,103 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.FileSystem.Index; +import org.sonar.api.batch.fs.InputFile; + +import static java.util.stream.Collectors.toList; + +/** + * @since 4.2 + */ +class AndPredicate extends AbstractFilePredicate implements OperatorPredicate { + + private final List<OptimizedFilePredicate> predicates = new ArrayList<>(); + + private AndPredicate() { + } + + public static FilePredicate create(Collection<FilePredicate> predicates) { + if (predicates.isEmpty()) { + return TruePredicate.TRUE; + } + AndPredicate result = new AndPredicate(); + for (FilePredicate filePredicate : predicates) { + if (filePredicate == TruePredicate.TRUE) { + continue; + } else if (filePredicate == FalsePredicate.FALSE) { + return FalsePredicate.FALSE; + } else if (filePredicate instanceof AndPredicate) { + result.predicates.addAll(((AndPredicate) filePredicate).predicates); + } else { + result.predicates.add(OptimizedFilePredicateAdapter.create(filePredicate)); + } + } + Collections.sort(result.predicates); + return result; + } + + @Override + public boolean apply(InputFile f) { + for (OptimizedFilePredicate predicate : predicates) { + if (!predicate.apply(f)) { + return false; + } + } + return true; + } + + @Override + public Iterable<InputFile> filter(Iterable<InputFile> target) { + Iterable<InputFile> result = target; + for (OptimizedFilePredicate predicate : predicates) { + result = predicate.filter(result); + } + return result; + } + + @Override + public Iterable<InputFile> get(Index index) { + if (predicates.isEmpty()) { + return index.inputFiles(); + } + // Optimization, use get on first predicate then filter with next predicates + Iterable<InputFile> result = predicates.get(0).get(index); + for (int i = 1; i < predicates.size(); i++) { + result = predicates.get(i).filter(result); + } + return result; + } + + Collection<OptimizedFilePredicate> predicates() { + return predicates; + } + + @Override + public List<FilePredicate> operands() { + return predicates.stream().map(p -> (FilePredicate) p).collect(toList()); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/DefaultFilePredicates.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/DefaultFilePredicates.java new file mode 100644 index 00000000000..60bb37e9896 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/DefaultFilePredicates.java @@ -0,0 +1,214 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import java.io.File; +import java.net.URI; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.FilePredicates; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputFile.Status; +import org.sonar.scanner.fs.PathPattern; + +/** + * Factory of {@link FilePredicate} + * + * @since 4.2 + */ +public class DefaultFilePredicates implements FilePredicates { + + private final Path baseDir; + + /** + * Client code should use {@link org.sonar.api.batch.fs.FileSystem#predicates()} to get an instance + */ + public DefaultFilePredicates(Path baseDir) { + this.baseDir = baseDir; + } + + /** + * Returns a predicate that always evaluates to true + */ + @Override + public FilePredicate all() { + return TruePredicate.TRUE; + } + + /** + * Returns a predicate that always evaluates to false + */ + @Override + public FilePredicate none() { + return FalsePredicate.FALSE; + } + + @Override + public FilePredicate hasAbsolutePath(String s) { + return new AbsolutePathPredicate(s, baseDir); + } + + /** + * non-normalized path and Windows-style path are supported + */ + @Override + public FilePredicate hasRelativePath(String s) { + return new RelativePathPredicate(s); + } + + @Override + public FilePredicate hasFilename(String s) { + return new FilenamePredicate(s); + } + + @Override + public FilePredicate hasExtension(String s) { + return new FileExtensionPredicate(s); + } + + @Override + public FilePredicate hasURI(URI uri) { + return new URIPredicate(uri, baseDir); + } + + @Override + public FilePredicate matchesPathPattern(String inclusionPattern) { + return new PathPatternPredicate(PathPattern.create(inclusionPattern)); + } + + @Override + public FilePredicate matchesPathPatterns(String[] inclusionPatterns) { + if (inclusionPatterns.length == 0) { + return TruePredicate.TRUE; + } + FilePredicate[] predicates = new FilePredicate[inclusionPatterns.length]; + for (int i = 0; i < inclusionPatterns.length; i++) { + predicates[i] = new PathPatternPredicate(PathPattern.create(inclusionPatterns[i])); + } + return or(predicates); + } + + @Override + public FilePredicate doesNotMatchPathPattern(String exclusionPattern) { + return not(matchesPathPattern(exclusionPattern)); + } + + @Override + public FilePredicate doesNotMatchPathPatterns(String[] exclusionPatterns) { + if (exclusionPatterns.length == 0) { + return TruePredicate.TRUE; + } + return not(matchesPathPatterns(exclusionPatterns)); + } + + @Override + public FilePredicate hasPath(String s) { + File file = new File(s); + if (file.isAbsolute()) { + return hasAbsolutePath(s); + } + return hasRelativePath(s); + } + + @Override + public FilePredicate is(File ioFile) { + if (ioFile.isAbsolute()) { + return hasAbsolutePath(ioFile.getAbsolutePath()); + } + return hasRelativePath(ioFile.getPath()); + } + + @Override + public FilePredicate hasLanguage(String language) { + return new LanguagePredicate(language); + } + + @Override + public FilePredicate hasLanguages(Collection<String> languages) { + List<FilePredicate> list = new ArrayList<>(); + for (String language : languages) { + list.add(hasLanguage(language)); + } + return or(list); + } + + @Override + public FilePredicate hasLanguages(String... languages) { + List<FilePredicate> list = new ArrayList<>(); + for (String language : languages) { + list.add(hasLanguage(language)); + } + return or(list); + } + + @Override + public FilePredicate hasType(InputFile.Type type) { + return new TypePredicate(type); + } + + @Override + public FilePredicate not(FilePredicate p) { + return new NotPredicate(p); + } + + @Override + public FilePredicate or(Collection<FilePredicate> or) { + return OrPredicate.create(or); + } + + @Override + public FilePredicate or(FilePredicate... or) { + return OrPredicate.create(Arrays.asList(or)); + } + + @Override + public FilePredicate or(FilePredicate first, FilePredicate second) { + return OrPredicate.create(Arrays.asList(first, second)); + } + + @Override + public FilePredicate and(Collection<FilePredicate> and) { + return AndPredicate.create(and); + } + + @Override + public FilePredicate and(FilePredicate... and) { + return AndPredicate.create(Arrays.asList(and)); + } + + @Override + public FilePredicate and(FilePredicate first, FilePredicate second) { + return AndPredicate.create(Arrays.asList(first, second)); + } + + @Override + public FilePredicate hasStatus(Status status) { + return new StatusPredicate(status); + } + + @Override + public FilePredicate hasAnyStatus() { + return new StatusPredicate(null); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/FalsePredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/FalsePredicate.java new file mode 100644 index 00000000000..e76bb84e6ae --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/FalsePredicate.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import java.util.Collections; +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.FileSystem.Index; +import org.sonar.api.batch.fs.InputFile; + +class FalsePredicate extends AbstractFilePredicate { + + static final FilePredicate FALSE = new FalsePredicate(); + + @Override + public boolean apply(InputFile inputFile) { + return false; + } + + @Override + public Iterable<InputFile> filter(Iterable<InputFile> target) { + return Collections.emptyList(); + } + + @Override + public Iterable<InputFile> get(Index index) { + return Collections.emptyList(); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/FileExtensionPredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/FileExtensionPredicate.java new file mode 100644 index 00000000000..46f257f25a4 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/FileExtensionPredicate.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import java.util.Locale; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; + +/** + * @since 6.3 + */ +public class FileExtensionPredicate extends AbstractFilePredicate { + + private final String extension; + + public FileExtensionPredicate(String extension) { + this.extension = lowercase(extension); + } + + @Override + public boolean apply(InputFile inputFile) { + return extension.equals(getExtension(inputFile)); + } + + @Override + public Iterable<InputFile> get(FileSystem.Index index) { + return index.getFilesByExtension(extension); + } + + public static String getExtension(InputFile inputFile) { + return getExtension(inputFile.filename()); + } + + static String getExtension(String name) { + int index = name.lastIndexOf('.'); + if (index < 0) { + return ""; + } + return lowercase(name.substring(index + 1)); + } + + private static String lowercase(String extension) { + return extension.toLowerCase(Locale.ENGLISH); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/FilenamePredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/FilenamePredicate.java new file mode 100644 index 00000000000..a99d9314c77 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/FilenamePredicate.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; + +/** + * @since 6.3 + */ +public class FilenamePredicate extends AbstractFilePredicate { + private final String filename; + + public FilenamePredicate(String filename) { + this.filename = filename; + } + + @Override + public boolean apply(InputFile inputFile) { + return filename.equals(inputFile.filename()); + } + + @Override + public Iterable<InputFile> get(FileSystem.Index index) { + return index.getFilesByName(filename); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/LanguagePredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/LanguagePredicate.java new file mode 100644 index 00000000000..5492e224558 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/LanguagePredicate.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import org.sonar.api.batch.fs.InputFile; + +/** + * @since 4.2 + */ +class LanguagePredicate extends AbstractFilePredicate { + private final String language; + + LanguagePredicate(String language) { + this.language = language; + } + + @Override + public boolean apply(InputFile f) { + return language.equals(f.language()); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/NotPredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/NotPredicate.java new file mode 100644 index 00000000000..5bc68adfbe7 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/NotPredicate.java @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import java.util.Arrays; +import java.util.List; +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.scanner.fs.predicates.AbstractFilePredicate; +import org.sonar.scanner.fs.predicates.OperatorPredicate; + +/** + * @since 4.2 + */ +class NotPredicate extends AbstractFilePredicate implements OperatorPredicate { + + private final FilePredicate predicate; + + NotPredicate(FilePredicate predicate) { + this.predicate = predicate; + } + + @Override + public boolean apply(InputFile f) { + return !predicate.apply(f); + } + + @Override + public List<FilePredicate> operands() { + return Arrays.asList(predicate); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/OperatorPredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/OperatorPredicate.java new file mode 100644 index 00000000000..c4493091865 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/OperatorPredicate.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import java.util.List; +import org.sonar.api.batch.fs.FilePredicate; + +/** + * A predicate that associate other predicates + */ +public interface OperatorPredicate extends FilePredicate { + + List<FilePredicate> operands(); + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/OptimizedFilePredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/OptimizedFilePredicate.java new file mode 100644 index 00000000000..9b5216d5549 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/OptimizedFilePredicate.java @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.InputFile; + +/** + * Optimized version of FilePredicate allowing to speed up query by looking at InputFile by index. + */ +public interface OptimizedFilePredicate extends FilePredicate, Comparable<OptimizedFilePredicate> { + + /** + * Filter provided files to keep only the ones that are valid for this predicate + */ + Iterable<InputFile> filter(Iterable<InputFile> inputFiles); + + /** + * Get all files that are valid for this predicate. + */ + Iterable<InputFile> get(FileSystem.Index index); + + /** + * For optimization. FilePredicates will be applied in priority order. For example when doing + * p.and(p1, p2, p3) then p1, p2 and p3 will be applied according to their priority value. Higher priority value + * are applied first. + * Assign a high priority when the predicate will likely highly reduce the set of InputFiles to filter. Also + * {@link RelativePathPredicate} and AbsolutePathPredicate have a high priority since they are using cache index. + */ + int priority(); +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/OptimizedFilePredicateAdapter.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/OptimizedFilePredicateAdapter.java new file mode 100644 index 00000000000..ba46aff6847 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/OptimizedFilePredicateAdapter.java @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.InputFile; + +public class OptimizedFilePredicateAdapter extends AbstractFilePredicate { + + private FilePredicate unoptimizedPredicate; + + private OptimizedFilePredicateAdapter(FilePredicate unoptimizedPredicate) { + this.unoptimizedPredicate = unoptimizedPredicate; + } + + @Override + public boolean apply(InputFile inputFile) { + return unoptimizedPredicate.apply(inputFile); + } + + public static OptimizedFilePredicate create(FilePredicate predicate) { + if (predicate instanceof OptimizedFilePredicate) { + return (OptimizedFilePredicate) predicate; + } else { + return new OptimizedFilePredicateAdapter(predicate); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/OrPredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/OrPredicate.java new file mode 100644 index 00000000000..2690a1261d9 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/OrPredicate.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.InputFile; + +/** + * @since 4.2 + */ +class OrPredicate extends AbstractFilePredicate implements OperatorPredicate { + + private final List<FilePredicate> predicates = new ArrayList<>(); + + private OrPredicate() { + } + + public static FilePredicate create(Collection<FilePredicate> predicates) { + if (predicates.isEmpty()) { + return TruePredicate.TRUE; + } + OrPredicate result = new OrPredicate(); + for (FilePredicate filePredicate : predicates) { + if (filePredicate == TruePredicate.TRUE) { + return TruePredicate.TRUE; + } else if (filePredicate == FalsePredicate.FALSE) { + continue; + } else if (filePredicate instanceof OrPredicate) { + result.predicates.addAll(((OrPredicate) filePredicate).predicates); + } else { + result.predicates.add(filePredicate); + } + } + return result; + } + + @Override + public boolean apply(InputFile f) { + for (FilePredicate predicate : predicates) { + if (predicate.apply(f)) { + return true; + } + } + return false; + } + + Collection<FilePredicate> predicates() { + return predicates; + } + + @Override + public List<FilePredicate> operands() { + return predicates; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/PathPatternPredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/PathPatternPredicate.java new file mode 100644 index 00000000000..0fdabcbcd31 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/PathPatternPredicate.java @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import java.nio.file.Paths; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.scanner.fs.PathPattern; + +/** + * @since 4.2 + */ +class PathPatternPredicate extends AbstractFilePredicate { + + private final PathPattern pattern; + + PathPatternPredicate(PathPattern pattern) { + this.pattern = pattern; + } + + @Override + public boolean apply(InputFile f) { + return pattern.match(f.path(), Paths.get(f.relativePath())); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/RelativePathPredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/RelativePathPredicate.java new file mode 100644 index 00000000000..d79221ea7f3 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/RelativePathPredicate.java @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import java.util.Collections; +import javax.annotation.Nullable; +import org.sonar.api.batch.fs.FileSystem.Index; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.utils.PathUtils; + +/** + * @since 4.2 + */ +public class RelativePathPredicate extends AbstractFilePredicate { + + @Nullable + private final String path; + + RelativePathPredicate(String path) { + this.path = PathUtils.sanitize(path); + } + + public String path() { + return path; + } + + @Override + public boolean apply(InputFile f) { + if (path == null) { + return false; + } + + return path.equals(f.relativePath()); + } + + @Override + public Iterable<InputFile> get(Index index) { + if (path != null) { + InputFile f = index.inputFile(this.path); + if (f != null) { + return Collections.singletonList(f); + } + } + return Collections.emptyList(); + } + + @Override + public int priority() { + return USE_INDEX; + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/StatusPredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/StatusPredicate.java new file mode 100644 index 00000000000..fb88b965a07 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/StatusPredicate.java @@ -0,0 +1,43 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import javax.annotation.Nullable; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.scanner.fs.predicates.AbstractFilePredicate; + +/** + * @deprecated since 7.8 + */ +@Deprecated +public class StatusPredicate extends AbstractFilePredicate { + + private final InputFile.Status status; + + StatusPredicate(@Nullable InputFile.Status status) { + this.status = status; + } + + @Override + public boolean apply(InputFile f) { + return status == null || status == f.status(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/TruePredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/TruePredicate.java new file mode 100644 index 00000000000..562515ce06e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/TruePredicate.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.FileSystem.Index; +import org.sonar.api.batch.fs.InputFile; + +class TruePredicate extends AbstractFilePredicate { + + static final FilePredicate TRUE = new TruePredicate(); + + @Override + public boolean apply(InputFile inputFile) { + return true; + } + + @Override + public Iterable<InputFile> get(Index index) { + return index.inputFiles(); + } + + @Override + public Iterable<InputFile> filter(Iterable<InputFile> target) { + return target; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/TypePredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/TypePredicate.java new file mode 100644 index 00000000000..3cddc59ad17 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/TypePredicate.java @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import org.sonar.api.batch.fs.InputFile; + +/** + * @since 4.2 + */ +class TypePredicate extends AbstractFilePredicate { + + private final InputFile.Type type; + + TypePredicate(InputFile.Type type) { + this.type = type; + } + + @Override + public boolean apply(InputFile f) { + return type == f.type(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/URIPredicate.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/URIPredicate.java new file mode 100644 index 00000000000..9e25267de05 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/fs/predicates/URIPredicate.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.fs.predicates; + +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; +import org.sonar.api.batch.fs.FileSystem.Index; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.scan.filesystem.PathResolver; + +/** + * @since 6.6 + */ +class URIPredicate extends AbstractFilePredicate { + + private final URI uri; + private final Path baseDir; + + URIPredicate(URI uri, Path baseDir) { + this.baseDir = baseDir; + this.uri = uri; + } + + @Override + public boolean apply(InputFile f) { + return uri.equals(f.uri()); + } + + @Override + public Iterable<InputFile> get(Index index) { + Path path = Paths.get(uri); + Optional<String> relative = PathResolver.relativize(baseDir, path); + if (!relative.isPresent()) { + return Collections.emptyList(); + } + InputFile f = index.inputFile(relative.get()); + return f != null ? Arrays.asList(f) : Collections.<InputFile>emptyList(); + } + + @Override + public int priority() { + return USE_INDEX; + } +} |