/*
* 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());
}
}