org.sonarlint.eclipse.jdt.internal.JdtUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.sonarlint.eclipse.jdt.internal.JdtUtils.java

Source

/*
 * SonarLint for Eclipse
 * Copyright (C) 2015-2017 SonarSource SA
 * sonarlint@sonarsource.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.sonarlint.eclipse.jdt.internal;

import java.io.File;
import javax.annotation.CheckForNull;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.sonarlint.eclipse.core.SonarLintLogger;
import org.sonarlint.eclipse.core.analysis.IPreAnalysisContext;

public class JdtUtils {

    public void configure(IPreAnalysisContext context, IProgressMonitor monitor) {
        IProject project = (IProject) context.getProject().getResource();
        if (project != null) {
            IJavaProject javaProject = JavaCore.create(project);
            configureJavaProject(javaProject, context);
        }
    }

    static boolean hasJavaNature(IProject project) {
        try {
            return project.hasNature(JavaCore.NATURE_ID);
        } catch (CoreException e) {
            SonarLintLogger.get().error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * SLE-34 Remove Java files that are not compiled.This should automatically exclude files that are excluded / unparseable. 
     */
    public static boolean isValidJavaFile(IFile file) {
        boolean hasJavaNature = hasJavaNature(file.getProject());
        IJavaProject javaProject = JavaCore.create(file.getProject());
        IJavaElement javaElt = JavaCore.create(file);
        return javaElt == null
                || (hasJavaNature && isStructureKnown(javaElt) && javaProject.isOnClasspath(javaElt));
    }

    private static boolean isStructureKnown(IJavaElement javaElt) {
        try {
            return javaElt.isStructureKnown();
        } catch (JavaModelException e) {
            return false;
        }
    }

    // Visible for testing
    public void configureJavaProject(IJavaProject javaProject, IPreAnalysisContext context) {
        String javaSource = javaProject.getOption(JavaCore.COMPILER_SOURCE, true);
        String javaTarget = javaProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true);

        context.setAnalysisProperty("sonar.java.source", javaSource);
        context.setAnalysisProperty("sonar.java.target", javaTarget);

        try {
            JavaProjectConfiguration configuration = new JavaProjectConfiguration();
            configuration.dependentProjects().add(javaProject);
            addClassPathToSonarProject(javaProject, configuration, true);
            configurationToProperties(context, configuration);
        } catch (JavaModelException e) {
            SonarLintLogger.get().error(e.getMessage(), e);
        }
    }

    /**
     * Adds the classpath of an eclipse project to the sonarProject recursively, i.e
     * it iterates all dependent projects. Libraries and output folders of dependent projects
     * are added, but no source folders.
     * @param javaProject the eclipse project to get the classpath from
     * @param sonarProjectProperties the sonar project properties to add the classpath to
     * @param context
     * @param topProject indicate we are working on the project to be analyzed and not on a dependent project
     * @throws JavaModelException see {@link IJavaProject#getResolvedClasspath(boolean)}
     */
    private static void addClassPathToSonarProject(IJavaProject javaProject, JavaProjectConfiguration context,
            boolean topProject) throws JavaModelException {
        IClasspathEntry[] classPath = javaProject.getResolvedClasspath(true);
        for (IClasspathEntry entry : classPath) {
            switch (entry.getEntryKind()) {
            case IClasspathEntry.CPE_SOURCE:
                processSourceEntry(entry, context, topProject);
                break;
            case IClasspathEntry.CPE_LIBRARY:
                processLibraryEntry(entry, javaProject, context, topProject);
                break;
            case IClasspathEntry.CPE_PROJECT:
                processProjectEntry(entry, javaProject, context);
                break;
            default:
                SonarLintLogger.get().info("Unhandled ClassPathEntry : " + entry);
                break;
            }
        }

        processOutputDir(javaProject.getOutputLocation(), context, topProject);
    }

    @CheckForNull
    protected static String getAbsolutePathAsString(IPath path) {
        IPath absolutePath = getAbsolutePath(path);
        return absolutePath != null ? absolutePath.toString() : null;
    }

    @CheckForNull
    private static IPath getAbsolutePath(IPath path) {
        // IPath should be resolved this way in order to handle linked resources (SONARIDE-271)
        IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
        IResource res = root.findMember(path);
        if (res != null) {
            if (res.getLocation() != null) {
                return pathIfExist(res.getLocation());
            } else {
                SonarLintLogger.get().error("Unable to resolve absolute path for " + res.getLocationURI());
                return null;
            }
        } else {
            return pathIfExist(path);
        }
    }

    private static IPath pathIfExist(IPath path) {
        File file = path.toFile();
        if (file.exists()) {
            return path;
        }
        return null;
    }

    private static void processOutputDir(IPath outputDir, JavaProjectConfiguration context, boolean topProject)
            throws JavaModelException {
        String outDir = getAbsolutePathAsString(outputDir);
        if (outDir != null) {
            if (topProject) {
                context.binaries().add(outDir);
            } else {
                // Output dir of dependents projects should be considered as libraries
                context.libraries().add(outDir);
            }
        } else {
            SonarLintLogger.get().info("Binary directory '" + outputDir
                    + "' was not added because it was not found. Maybe you should enable auto build of your project.");
        }
    }

    private static void processSourceEntry(IClasspathEntry entry, JavaProjectConfiguration context,
            boolean topProject) throws JavaModelException {
        if (isSourceExcluded(entry)) {
            return;
        }
        if (entry.getOutputLocation() != null) {
            processOutputDir(entry.getOutputLocation(), context, topProject);
        }
    }

    private static void processLibraryEntry(IClasspathEntry entry, IJavaProject javaProject,
            JavaProjectConfiguration context, boolean topProject) throws JavaModelException {
        if (topProject || entry.isExported()) {
            final String libPath = resolveLibrary(javaProject, entry);
            if (libPath != null) {
                context.libraries().add(libPath);
            }
        }
    }

    private static void processProjectEntry(IClasspathEntry entry, IJavaProject javaProject,
            JavaProjectConfiguration context) throws JavaModelException {
        IJavaModel javaModel = javaProject.getJavaModel();
        IJavaProject referredProject = javaModel.getJavaProject(entry.getPath().segment(0));
        if (!context.dependentProjects().contains(referredProject)) {
            context.dependentProjects().add(referredProject);
            addClassPathToSonarProject(referredProject, context, false);
        }
    }

    private static String resolveLibrary(IJavaProject javaProject, IClasspathEntry entry) {
        final String libPath;
        IResource member = findPath(javaProject.getProject(), entry.getPath());
        if (member != null) {
            libPath = member.getLocation().toOSString();
        } else {
            libPath = entry.getPath().makeAbsolute().toOSString();
        }
        if (!new File(libPath).exists()) {
            return null;
        }
        return libPath.endsWith(File.separator) ? libPath.substring(0, libPath.length() - 1) : libPath;
    }

    private static IResource findPath(IProject project, IPath path) {
        IResource member = project.findMember(path);
        if (member == null) {
            IWorkspaceRoot workSpaceRoot = project.getWorkspace().getRoot();
            member = workSpaceRoot.findMember(path);
        }
        return member;
    }

    /**
     * Allows to determine directories with resources to exclude them from analysis, otherwise analysis might fail due to SONAR-791.
     * This is a kind of workaround, which is based on the fact that M2Eclipse configures exclusion pattern "**" for directories with resources.
     */
    private static boolean isSourceExcluded(IClasspathEntry entry) {
        IPath[] exclusionPatterns = entry.getExclusionPatterns();
        if (exclusionPatterns != null) {
            for (IPath exclusionPattern : exclusionPatterns) {
                if ("**".equals(exclusionPattern.toString())) {
                    return true;
                }
            }
        }
        return false;
    }

    private static void configurationToProperties(IPreAnalysisContext analysisContext,
            JavaProjectConfiguration context) {
        analysisContext.setAnalysisProperty("sonar.libraries", context.libraries());
        // Eclipse doesn't separate main and test classpath
        analysisContext.setAnalysisProperty("sonar.java.libraries", context.libraries());
        analysisContext.setAnalysisProperty("sonar.java.test.libraries", context.libraries());
        analysisContext.setAnalysisProperty("sonar.binaries", context.binaries());
        // Eclipse doesn't separate main and test classpath
        analysisContext.setAnalysisProperty("sonar.java.binaries", context.binaries());
        analysisContext.setAnalysisProperty("sonar.java.test.binaries", context.binaries());
    }
}