Skip to content

Commit

Permalink
Automatically detect jars in lib/ folder
Browse files Browse the repository at this point in the history
... in the root directory of  standalone files / invisible projects

- a file watcher on lib/** is added for invisible projects
- source for foo.jar is automatically detected if there's a
foo-sources.jar in the lib/ folder
- multiple file events are handled so that classpath modifications are
minimized

Signed-off-by: Fred Bricon <fbricon@gmail.com>
  • Loading branch information
fbricon committed Feb 7, 2019
1 parent b73c63a commit f3c976a
Show file tree
Hide file tree
Showing 16 changed files with 763 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,22 @@
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
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.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand All @@ -24,6 +36,7 @@
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.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
Expand All @@ -39,8 +52,13 @@
*/
@SuppressWarnings("restriction")
public final class ProjectUtils {

public static final String WORKSPACE_LINK = "_";

private static final String JAR_SUFFIX = ".jar";

private static final String SOURCE_JAR_SUFFIX = "-sources.jar";

private ProjectUtils() {
//No instanciation
}
Expand Down Expand Up @@ -275,4 +293,80 @@ private static IClasspathEntry removeFilters(IClasspathEntry entry, IPath path)
return JavaCore.newSourceEntry(entry.getPath(), inclusionList.toArray(new IPath[0]), exclusionList.toArray(new IPath[0]), entry.getOutputLocation(), entry.getExtraAttributes());
}
}

public static void updateBinaries(IJavaProject javaProject, IPath libFolderPath, IProgressMonitor monitor) throws CoreException {
updateBinaries(javaProject, Collections.singleton(libFolderPath), monitor);
}

public static void updateBinaries(IJavaProject javaProject, Set<IPath> libFolderPaths, IProgressMonitor monitor) throws CoreException {
Set<Path> binaries = collectBinaries(libFolderPaths, monitor);
if (monitor.isCanceled()) {
return;
}
IClasspathEntry[] rawClasspath = javaProject.getRawClasspath();
List<IClasspathEntry> newEntries = Arrays.stream(rawClasspath).filter(cpe -> cpe.getEntryKind() != IClasspathEntry.CPE_LIBRARY).collect(Collectors.toCollection(ArrayList::new));

for (Path file : binaries) {
if (monitor.isCanceled()) {
return;
}
IPath newLibPath = new org.eclipse.core.runtime.Path(file.toString());
IPath sourcePath = detectSources(file);
IClasspathEntry newEntry = JavaCore.newLibraryEntry(newLibPath, sourcePath, null);
JavaLanguageServerPlugin.logInfo("Adding " + newLibPath + " to the classpath");
newEntries.add(newEntry);
}
IClasspathEntry[] newClasspath = newEntries.toArray(new IClasspathEntry[newEntries.size()]);
if (!rawClasspath.equals(newClasspath)) {
javaProject.setRawClasspath(newClasspath, monitor);
}
}

private static Set<Path> collectBinaries(Set<IPath> libFolderPaths, IProgressMonitor monitor) {
Set<Path> binaries = new LinkedHashSet<>();
FileVisitor<? super Path> jarDetector = new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (monitor.isCanceled()) {
return FileVisitResult.TERMINATE;
}
if (isBinary(file)) {
binaries.add(file);
}
return FileVisitResult.CONTINUE;
}

};
for (IPath libFolderPath : libFolderPaths) {
String path = libFolderPath.toOSString();
try {
Path libFolder = Paths.get(path);
if (!Files.isDirectory(libFolder)) {
continue;
}
Files.walkFileTree(Paths.get(path), jarDetector);
} catch (IOException e) {
new CoreException(StatusFactory.newErrorStatus("Unable to analyze " + path, e));
}
}
return binaries;
}

private static boolean isBinary(Path file) {
String fileName = file.getFileName().toString();
return (fileName.endsWith(JAR_SUFFIX)
//skip source jar files
//more robust approach would be to check if jar contains .class files or not
&& !fileName.endsWith(SOURCE_JAR_SUFFIX));
}

private static IPath detectSources(Path file) {
String filename = file.getFileName().toString();
//better approach would be to (also) resolve sources using Maven central, or anything smarter really
String sourceName = filename.substring(0, filename.lastIndexOf(JAR_SUFFIX)) + SOURCE_JAR_SUFFIX;
Path sourcePath = file.getParent().resolve(sourceName);
return Files.isRegularFile(sourcePath) ? new org.eclipse.core.runtime.Path(sourcePath.toString()) : null;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*******************************************************************************
* Copyright (c) 2019 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.managers;

import org.eclipse.core.resources.IProject;

public class DefaultProjectBuildSupport extends EclipseBuildSupport implements IBuildSupport {

private ProjectsManager projectManager;

public DefaultProjectBuildSupport(ProjectsManager projectManager) {
this.projectManager = projectManager;
}

@Override
public boolean applies(IProject project) {
return projectManager.getDefaultProject().equals(project);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*******************************************************************************
* Copyright (c) 2019 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.managers;

import java.util.Set;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;

import com.google.common.collect.Sets;

public class EclipseBuildSupport implements IBuildSupport {

private Set<String> files = Sets.newHashSet(".classpath", ".project", ".factorypath");
private Set<String> folders = Sets.newHashSet(".settings");

@Override
public boolean applies(IProject project) {
return true; //all projects are Eclipse projects
}

@Override
public boolean isBuildFile(IResource resource) {
if (resource == null || resource.getProject() == null) {
return false;
}
IProject project = resource.getProject();
for (String file : files) {
if (resource.equals(project.getFolder(file))) {
return true;
}
}
IPath path = resource.getFullPath();
for (String folder : folders) {
IPath folderPath = project.getFolder(folder).getFullPath();
if (folderPath.isPrefixOf(path)) {
return true;
}
}
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager.CHANGE_TYPE;

/**
* @author Fred Bricon
Expand Down Expand Up @@ -58,7 +59,7 @@ public void update(IProject project, boolean force, IProgressMonitor monitor) th

@Override
public boolean isBuildFile(IResource resource) {
if (resource != null && resource.getType() == IResource.FILE && (resource.getName().endsWith(GRADLE_SUFFIX) || resource.getName().equals(GRADLE_PROPERTIES)) && resource.getProject() != null
if (resource != null && resource.getType() == IResource.FILE && (resource.getName().endsWith(GRADLE_SUFFIX) || resource.getName().equals(GRADLE_PROPERTIES))
&& ProjectUtils.isGradleProject(resource.getProject())) {
try {
if (!ProjectUtils.isJavaProject(resource.getProject())) {
Expand Down Expand Up @@ -92,6 +93,14 @@ public static void cleanGradleModels(IProgressMonitor monitor) {
}
}

@Override
public boolean fileChanged(IResource resource, CHANGE_TYPE changeType, IProgressMonitor monitor) throws CoreException {
if (resource == null || !applies(resource.getProject())) {
return false;
}
return IBuildSupport.super.fileChanged(resource, changeType, monitor) || isBuildFile(resource);
}

/**
* save gradle project preferences
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager.CHANGE_TYPE;

/**
* @author Fred Bricon
Expand All @@ -35,12 +36,44 @@ public interface IBuildSupport {
* @param monitor
* @throws CoreException
*/
void update(IProject resource, boolean force, IProgressMonitor monitor) throws CoreException;
default void update(IProject resource, boolean force, IProgressMonitor monitor) throws CoreException {
//do nothing
}

/**
* Is equal to a non-forced update: {@code update(resource, false, monitor)}
*/
default void update(IProject resource, IProgressMonitor monitor) throws CoreException {
update(resource, false, monitor);
}

/**
* Handle resource changes.
* @param resource
* - the resource that changed
* @param changeType
* - the type of change
* @param monitor
* - a progress monitor
* @return <code>true</code> if a project configuration update is recommended next
*
* @throws CoreException
*/
default boolean fileChanged(IResource resource, CHANGE_TYPE changeType, IProgressMonitor monitor) throws CoreException {
refresh(resource, changeType, monitor);
return false;
}

default void refresh(IResource resource, CHANGE_TYPE changeType, IProgressMonitor monitor) throws CoreException {
if (resource == null) {
return;
}
if (changeType == CHANGE_TYPE.DELETED) {
resource = resource.getParent();
}
if (resource != null) {
resource.refreshLocal(IResource.DEPTH_INFINITE, monitor);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*******************************************************************************
* Copyright (c) 2019 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.managers;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager.CHANGE_TYPE;

/**
* @author Fred Bricon
*
*/
public class InvisibleProjectBuildSupport extends EclipseBuildSupport implements IBuildSupport {

static final String LIB_FOLDER = "lib";

private UpdateClasspathJob updateClasspathJob;

public InvisibleProjectBuildSupport() {
this(UpdateClasspathJob.getInstance());
}

public InvisibleProjectBuildSupport(UpdateClasspathJob updateClasspathJob) {
this.updateClasspathJob = updateClasspathJob;
}

@Override
public boolean applies(IProject project) {
return project != null && project.isAccessible() && !ProjectUtils.isVisibleProject(project);
}

@Override
public boolean fileChanged(IResource resource, CHANGE_TYPE changeType, IProgressMonitor monitor) throws CoreException {
if (resource == null || !applies(resource.getProject())) {
return false;
}
refresh(resource, changeType, monitor);
IProject invisibleProject = resource.getProject();
IPath realFolderPath = invisibleProject.getFolder(ProjectUtils.WORKSPACE_LINK).getLocation();
if (realFolderPath != null) {
IPath libFolderPath = realFolderPath.append(LIB_FOLDER);
if (libFolderPath.isPrefixOf(resource.getLocation())) {
updateClasspathJob.updateClasspath(JavaCore.create(invisibleProject), libFolderPath);
}
}
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.ls.core.internal.AbstractProjectImporter;
Expand Down Expand Up @@ -78,25 +79,30 @@ public void importToWorkspace(IProgressMonitor monitor) throws OperationCanceled

String invisibleProjectName = ProjectUtils.getWorkspaceInvisibleProjectName(rootPath);
IProject invisibleProject = ResourcesPlugin.getWorkspace().getRoot().getProject(invisibleProjectName);
IPath libFolder = new Path(InvisibleProjectBuildSupport.LIB_FOLDER);
IFolder workspaceLinkFolder = invisibleProject.getFolder(ProjectUtils.WORKSPACE_LINK);
IPath relativeSourcePath = sourceDirectory.makeRelativeTo(rootPath);
IPath sourcePath = relativeSourcePath.isEmpty() ? workspaceLinkFolder.getFullPath() : workspaceLinkFolder.getFolder(relativeSourcePath).getFullPath();
if (!invisibleProject.exists()) {
try {
JavaLanguageServerPlugin.logInfo("Try to create an invisible project for the workspace " + rootPath);
invisibleProject = ProjectUtils.createInvisibleProjectIfNotExist(rootPath);
IFolder workspaceLinkFolder = invisibleProject.getFolder(ProjectUtils.WORKSPACE_LINK);
IPath relativeSourcePath = sourceDirectory.makeRelativeTo(rootPath);
IPath sourcePath = relativeSourcePath.isEmpty() ? workspaceLinkFolder.getFullPath() : workspaceLinkFolder.getFolder(relativeSourcePath).getFullPath();
List<IProject> subProjects = ProjectUtils.getVisibleProjects(rootPath);
List<IPath> subProjectPaths = subProjects.stream().map(project -> {
IPath relativePath = project.getLocation().makeRelativeTo(rootPath);
return workspaceLinkFolder.getFolder(relativePath).getFullPath();
}).collect(Collectors.toList());
subProjectPaths.add(libFolder);
IJavaProject javaProject = JavaCore.create(invisibleProject);
ProjectUtils.addSourcePath(sourcePath, subProjectPaths.toArray(new IPath[0]), javaProject);
JavaLanguageServerPlugin.logInfo("Successfully created a workspace invisible project " + invisibleProjectName);
} catch (CoreException e) {
JavaLanguageServerPlugin.logException("Failed to create the invisible project.", e);
return;
}
}
IJavaProject javaProject = JavaCore.create(invisibleProject);
ProjectUtils.updateBinaries(javaProject, rootPath.append(libFolder), monitor);
}

@Override
Expand Down
Loading

0 comments on commit f3c976a

Please sign in to comment.