org.sonar.java.classpath.AbstractClasspath Maven / Gradle / Ivy
The newest version!
/*
* SonarQube Java
* Copyright (C) 2012-2024 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 Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* 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 Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.classpath;
import java.io.File;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.ScannerSide;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.config.Configuration;
import org.sonar.java.collections.CollectionUtils;
import org.sonarsource.api.sonarlint.SonarLintSide;
@ScannerSide
@SonarLintSide
public abstract class AbstractClasspath {
private static final char UNIX_SEPARATOR = '/';
private static final char WINDOWS_SEPARATOR = '\\';
private static final Logger LOG = LoggerFactory.getLogger(AbstractClasspath.class);
protected final Configuration settings;
protected final FileSystem fs;
private final InputFile.Type fileType;
private static final Path[] STANDARD_CLASSES_DIRS = {Paths.get("target", "classes"), Paths.get("target", "test-classes")};
protected final List binaries;
protected final List elements;
protected boolean validateLibraries;
protected boolean initialized;
private boolean inAndroidContext = false;
protected AbstractClasspath(Configuration settings, FileSystem fs, InputFile.Type fileType) {
this.settings = settings;
this.fs = fs;
this.fileType = fileType;
this.binaries = new ArrayList<>();
this.elements = new ArrayList<>();
initialized = false;
}
protected List getJdkJars() {
List jdkClassesRoots = settings.get(ClasspathProperties.SONAR_JAVA_JDK_HOME)
.flatMap(AbstractClasspath::existingDirectoryOrLog)
.map(File::toPath)
.map(JavaSdkUtil::getJdkClassesRoots)
.orElse(Collections.emptyList());
logResolvedFiles(ClasspathProperties.SONAR_JAVA_JDK_HOME, jdkClassesRoots);
return jdkClassesRoots;
}
static void logResolvedFiles(String property, Collection files) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Property '%s' resolved with:%n%s", property, files.stream()
.map(File::getAbsolutePath)
.collect(Collectors.joining("," + System.lineSeparator(), "[", "]"))));
}
}
private static Optional existingDirectoryOrLog(String path) {
LOG.debug("Property '{}' set with: {}", ClasspathProperties.SONAR_JAVA_JDK_HOME, path);
File file = new File(path);
if (!file.exists() || !file.isDirectory()) {
LOG.warn("Invalid value for '{}' property, defaulting to runtime JDK.{}Configured location does not exists: '{}'",
ClasspathProperties.SONAR_JAVA_JDK_HOME, System.lineSeparator(), file.getAbsolutePath());
return Optional.empty();
}
return Optional.of(file);
}
protected abstract void init();
public abstract void logSuspiciousEmptyLibraries();
protected Set getFilesFromProperty(String property) {
Set result = new LinkedHashSet<>();
if (settings.hasKey(property)) {
List fileNames = Arrays.stream(settings.getStringArray(property))
.filter(s -> !s.isEmpty()).toList();
File baseDir = fs.baseDir();
boolean hasJavaSources = hasJavaSources();
boolean validateLibs = validateLibraries;
boolean isLibraryProperty = property.endsWith("libraries");
for (String pathPattern : fileNames) {
Set libraryFilesForPattern = getFilesForPattern(baseDir.toPath(), pathPattern, isLibraryProperty);
if (validateLibraries && libraryFilesForPattern.isEmpty() && hasJavaSources) {
LOG.error("Invalid value for '{}' property.", property);
String message = "No files nor directories matching '" + pathPattern + "'";
throw new IllegalStateException(message);
}
validateLibraries = validateLibs;
result.addAll(libraryFilesForPattern);
}
if (result.stream().anyMatch(f -> f.getName().endsWith("android.jar"))) {
inAndroidContext = true;
}
}
return result;
}
protected boolean hasJavaSources() {
return fs.hasFiles(fs.predicates().and(fs.predicates().hasLanguage("java"), fs.predicates().hasType(fileType)));
}
protected boolean hasMoreThanOneJavaFile() {
return CollectionUtils.size(fs.inputFiles(fs.predicates().and(fs.predicates().hasLanguage("java"), fs.predicates().hasType(fileType)))) > 1;
}
private Set getFilesForPattern(Path baseDir, String pathPattern, boolean libraryProperty) {
try {
Path filePath = resolvePath(baseDir, pathPattern);
File file = filePath.toFile();
if (file.isFile()) {
return getMatchingFile(pathPattern, file);
}
if (file.isDirectory()) {
return getMatchesInDir(filePath, libraryProperty);
}
} catch (IOException | InvalidPathException e) {
// continue
}
String dirPath = sanitizeWildcards(pathPattern);
String fileNamePattern = pathPattern;
int lastPathSeparator = Math.max(dirPath.lastIndexOf(UNIX_SEPARATOR), dirPath.lastIndexOf(WINDOWS_SEPARATOR));
if (lastPathSeparator == -1) {
dirPath = ".";
} else {
dirPath = pathPattern.substring(0, lastPathSeparator);
fileNamePattern = pathPattern.substring(lastPathSeparator + 1);
}
Path dir = resolvePath(baseDir, dirPath);
return getFilesInDir(dir, fileNamePattern, libraryProperty);
}
private static Set getFilesInDir(Path dir, String fileNamePattern, boolean libraryProperty) {
if (!dir.toFile().isDirectory()) {
return Collections.emptySet();
}
try {
if (libraryProperty) {
return getMatchingLibraries(fileNamePattern, dir);
} else {
return getMatchingDirs(fileNamePattern, dir);
}
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
private static String sanitizeWildcards(String pathPattern) {
int wildcardIndex = pathPattern.indexOf('*');
if (wildcardIndex >= 0) {
return pathPattern.substring(0, wildcardIndex);
}
return pathPattern;
}
private Set getMatchingFile(String pathPattern, File file) {
if (pathPattern.endsWith(".jar") || pathPattern.endsWith(".zip") || pathPattern.endsWith(".aar")) {
return Collections.singleton(file);
}
LOG.debug("File {} was ignored from java classpath", file.getAbsolutePath());
validateLibraries = false;
return Collections.emptySet();
}
private static Set getMatchingDirs(String pattern, Path dir) throws IOException {
if (!StringUtils.isEmpty(pattern)) {
// find all dirs and subdirs that match the pattern
PathMatcher matcher = FileSystems.getDefault().getPathMatcher(getGlob(dir, pattern));
return new DirFinder().find(dir, matcher);
} else {
// no pattern, so we just return dir
return Collections.singleton(dir.toFile());
}
}
private static Set getMatchesInDir(Path dirPath, boolean isLibraryProperty) throws IOException {
if (isLibraryProperty) {
for (Path end : STANDARD_CLASSES_DIRS) {
if (dirPath.endsWith(end)) {
// don't scan these, as they should only contain .classes with paths starting from the root
return Collections.singleton(dirPath.toFile());
}
}
Set matches = new LibraryFinder().find(dirPath, p -> true);
matches.add(dirPath.toFile());
return matches;
} else {
return Collections.singleton(dirPath.toFile());
}
}
private static String separatorsToUnix(final String path) {
return path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR);
}
private static String getGlob(Path dir, String pattern) {
// globs work with unix separators
return "glob:" + separatorsToUnix(dir.toString()) + UNIX_SEPARATOR + separatorsToUnix(pattern);
}
private static Set getMatchingLibraries(String pattern, Path dir) throws IOException {
Set matches = new LinkedHashSet<>();
Set dirs = getMatchingDirs(pattern, dir);
PathMatcher matcher = FileSystems.getDefault().getPathMatcher(getGlob(dir, pattern));
for (File d : dirs) {
matches.addAll(getLibs(d.toPath()));
}
matches.addAll(dirs);
matches.addAll(new LibraryFinder().find(dir, matcher));
if (pattern.startsWith("**/")) {
// match jar in the base dir when using wildcard
matches.addAll(new LibraryFinder().find(dir, FileSystems.getDefault().getPathMatcher(getGlob(dir, pattern.substring(3)))));
}
return matches;
}
private static List getLibs(Path dir) throws IOException {
Filter filter = path -> {
String name = path.getFileName().toString();
return name.endsWith(".jar") || name.endsWith(".zip") || name.endsWith(".aar");
};
List files = new ArrayList<>();
try (DirectoryStream stream = Files.newDirectoryStream(dir, filter)) {
stream.forEach(p -> files.add(p.toFile()));
}
return files;
}
private abstract static class AbstractFileFinder extends SimpleFileVisitor {
protected Set matchedFiles = new LinkedHashSet<>();
protected PathMatcher matcher;
Set find(Path dir, PathMatcher matcher) throws IOException {
this.matcher = matcher;
Files.walkFileTree(dir, this);
return matchedFiles;
}
}
private static class DirFinder extends AbstractFileFinder {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
if (matcher.matches(dir)) {
matchedFiles.add(dir.toFile());
}
return FileVisitResult.CONTINUE;
}
}
private static class LibraryFinder extends AbstractFileFinder {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attr) {
String name = file.getFileName().toString();
if ((name.endsWith(".jar") || name.endsWith(".zip")) && matcher.matches(file)) {
matchedFiles.add(file.toFile());
}
return FileVisitResult.CONTINUE;
}
}
private static Path resolvePath(Path baseDir, String fileName) {
Path filePath = Paths.get(fileName);
if (!filePath.isAbsolute()) {
filePath = baseDir.resolve(fileName);
}
return filePath.normalize();
}
public List getElements() {
init();
return elements;
}
public List getBinaryDirs() {
init();
return binaries;
}
public boolean inAndroidContext() {
return inAndroidContext;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy