org.sonar.java.AbstractJavaClasspath.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.java.AbstractJavaClasspath.java

Source

/*
 * SonarQube Java
 * Copyright (C) 2012-2017 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.java;

import com.google.common.base.Splitter;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.batch.BatchSide;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.config.Settings;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonarsource.api.sonarlint.SonarLintSide;

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.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

@BatchSide
@SonarLintSide
public abstract class AbstractJavaClasspath {

    private static final char SEPARATOR = ',';
    private static final char UNIX_SEPARATOR = '/';
    private static final char WINDOWS_SEPARATOR = '\\';
    private static final Logger LOG = Loggers.get(AbstractJavaClasspath.class);
    protected final Settings 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 List<File> binaries;
    protected List<File> elements;
    protected boolean validateLibraries;
    protected boolean initialized;

    public AbstractJavaClasspath(Settings settings, FileSystem fs, InputFile.Type fileType) {
        this.settings = settings;
        this.fs = fs;
        this.fileType = fileType;
        initialized = false;
    }

    protected abstract void init();

    protected Set<File> getFilesFromProperty(String property) {
        Set<File> result = new LinkedHashSet<>();
        String fileList = settings.getString(property);
        if (StringUtils.isNotEmpty(fileList)) {
            Iterable<String> fileNames = Splitter.on(SEPARATOR).omitEmptyStrings().split(fileList);
            File baseDir = fs.baseDir();
            boolean hasJavaSources = hasJavaSources();
            boolean validateLibs = validateLibraries;
            boolean isLibraryProperty = property.endsWith("libraries");
            for (String pathPattern : fileNames) {
                Set<File> libraryFilesForPattern = getFilesForPattern(baseDir.toPath(), pathPattern,
                        isLibraryProperty);
                if (validateLibraries && libraryFilesForPattern.isEmpty() && hasJavaSources) {
                    LOG.error("Invalid value for " + property);
                    String message = "No files nor directories matching '" + pathPattern + "'";
                    throw new IllegalStateException(message);
                }
                validateLibraries = validateLibs;
                result.addAll(libraryFilesForPattern);
            }
        }
        return result;
    }

    protected boolean hasJavaSources() {
        return fs.hasFiles(
                fs.predicates().and(fs.predicates().hasLanguage("java"), fs.predicates().hasType(fileType)));
    }

    private Set<File> 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<File> 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<File> getMatchingFile(String pathPattern, File file) {
        if (pathPattern.endsWith(".jar") || pathPattern.endsWith(".zip") || pathPattern.endsWith(".aar")) {
            return Collections.singleton(file);
        }
        LOG.debug("File " + file.getAbsolutePath() + " was ignored from java classpath");
        validateLibraries = false;
        return Collections.emptySet();
    }

    private static Set<File> 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<File> 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<File> 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<File> getMatchingLibraries(String pattern, Path dir) throws IOException {
        Set<File> matches = new LinkedHashSet<>();
        Set<File> 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<File> getLibs(Path dir) throws IOException {
        Filter<Path> filter = path -> {
            String name = path.getFileName().toString();
            return name.endsWith(".jar") || name.endsWith(".zip") || name.endsWith(".aar");
        };

        List<File> files = new ArrayList<>();
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, filter)) {
            stream.forEach(p -> files.add(p.toFile()));
        }
        return files;
    }

    private abstract static class AbstractFileFinder extends SimpleFileVisitor<Path> {
        protected Set<File> matchedFiles = new LinkedHashSet<>();
        protected PathMatcher matcher;

        Set<File> 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<File> getElements() {
        init();
        return elements;
    }

    public List<File> getBinaryDirs() {
        init();
        return binaries;
    }
}