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 <[email protected]>
  • Loading branch information
fbricon committed Feb 7, 2019
1 parent b73c63a commit 905f39b
Show file tree
Hide file tree
Showing 16 changed files with 770 additions and 75 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016-2017 Red Hat Inc. and others.
* Copyright (c) 2016-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
Expand All @@ -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.getFile(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
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016-2017 Red Hat Inc. and others.
* Copyright (c) 2016-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
Expand Down 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
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016-2017 Red Hat Inc. and others.
* Copyright (c) 2016-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
Expand All @@ -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;
}

}
Loading

0 comments on commit 905f39b

Please sign in to comment.