Skip to content

Commit

Permalink
Support updating whole classpath of the project
Browse files Browse the repository at this point in the history
Signed-off-by: Sheng Chen <sheche@microsoft.com>
  • Loading branch information
jdneo committed Mar 18, 2024
1 parent 5367708 commit 4c29a57
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,7 @@ public Object executeCommand(String commandId, List<Object> arguments, IProgress
case "java.project.updateClassPaths": {
String projectUri = (String) arguments.get(0);
ProjectClasspathEntries entries = (JSONUtility.toModel(arguments.get(1), ProjectClasspathEntries.class));
Map<String, String> sourceAndOutput = new HashMap<>();
for (ProjectClasspathEntry entry : entries.getClasspathEntries()) {
if (entry.getKind() == IClasspathEntry.CPE_SOURCE) {
sourceAndOutput.put(entry.getPath(), entry.getOutput());
}
}
ProjectCommand.updateSourcePaths(projectUri, sourceAndOutput);
ProjectCommand.updateClasspaths(projectUri, entries.getClasspathEntries(), monitor);
return null;
}
case "java.project.isTestFile":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -700,11 +700,32 @@ public static void refreshDiagnostics(IProgressMonitor monitor) throws JavaModel
*/
public static IClasspathEntry[] resolveClassPathEntries(IJavaProject javaProject, Map<IPath, IPath> sourceAndOutput, List<IPath> excludingPaths, IPath outputPath) throws CoreException {
List<IClasspathEntry> newEntries = new LinkedList<>();
Map<IPath, IClasspathEntry> originalSources = new HashMap<>();
for (IClasspathEntry entry : javaProject.getRawClasspath()) {
if (entry.getEntryKind() != IClasspathEntry.CPE_SOURCE) {
newEntries.add(entry);
} else {
}
}

if (outputPath == null) {
outputPath = javaProject.getOutputLocation();
}

IClasspathEntry[] newSources = resolveSourceClasspathEntries(javaProject, sourceAndOutput, excludingPaths, outputPath);
newEntries.addAll(Arrays.asList(newSources));

IClasspathEntry[] rawClasspath = newEntries.toArray(IClasspathEntry[]::new);
IJavaModelStatus checkStatus = ClasspathEntry.validateClasspath(javaProject, rawClasspath, outputPath);
if (!checkStatus.isOK()) {
throw new CoreException(checkStatus);
}

return rawClasspath;
}

public static IClasspathEntry[] resolveSourceClasspathEntries(IJavaProject javaProject, Map<IPath, IPath> sourceAndOutput, List<IPath> excludingPaths, IPath outputPath) throws CoreException {
Map<IPath, IClasspathEntry> originalSources = new HashMap<>();
for (IClasspathEntry entry : javaProject.getRawClasspath()) {
if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
originalSources.put(entry.getPath(), entry);
}
}
Expand Down Expand Up @@ -757,15 +778,7 @@ public static IClasspathEntry[] resolveClassPathEntries(IJavaProject javaProject
}
}
}
newEntries.addAll(sourceEntries);

IClasspathEntry[] rawClasspath = newEntries.toArray(IClasspathEntry[]::new);
IJavaModelStatus checkStatus = ClasspathEntry.validateClasspath(javaProject, rawClasspath, outputPath);
if (!checkStatus.isOK()) {
throw new CoreException(checkStatus);
}

return rawClasspath;
return sourceEntries.toArray(IClasspathEntry[]::new);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
package org.eclipse.jdt.ls.core.internal.commands;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayDeque;
Expand All @@ -28,6 +30,8 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Stream;

import org.eclipse.core.resources.IContainer;
Expand All @@ -46,9 +50,11 @@
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModelStatus;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IParent;
import org.eclipse.jdt.core.IType;
Expand All @@ -71,6 +77,11 @@
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.m2e.jdt.IClasspathManager;
import org.eclipse.m2e.jdt.MavenJdtPlugin;
import org.eclipse.m2e.jdt.internal.BuildPathManager;
import org.eclipse.m2e.jdt.internal.MavenClasspathContainer;
import org.eclipse.m2e.jdt.internal.MavenClasspathContainerSaveHelper;

public class ProjectCommand {

Expand Down Expand Up @@ -147,22 +158,47 @@ public static Map<String, Object> getProjectSettings(String uri, List<String> se
settings.putIfAbsent(key, referencedLibraries);
break;
case CLASSPATH_ENTRIES:
IClasspathEntry[] entries = javaProject.getRawClasspath();
List<IClasspathEntry> entriesToBeScan = new LinkedList<>();
Collections.addAll(entriesToBeScan, javaProject.getRawClasspath());
List<ProjectClasspathEntry> classpathEntries = new LinkedList<>();
for (IClasspathEntry entry : entries) {
for (int i = 0; i < entriesToBeScan.size(); i++) {
IClasspathEntry entry = entriesToBeScan.get(i);
IPath path = entry.getPath();
IPath output = entry.getOutputLocation();
if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
int entryKind = entry.getEntryKind();
if (entryKind == IClasspathEntry.CPE_SOURCE) {
IPath relativePath = path.makeRelativeTo(project.getFullPath());
if (relativePath.isEmpty()) {
continue; // A valid relative source path should not be empty.
}
path = project.getFolder(path.makeRelativeTo(project.getFullPath())).getLocation();
if (output != null) {
output = project.getFolder(output.makeRelativeTo(project.getFullPath())).getLocation();
}
} else if (entryKind == IClasspathEntry.CPE_CONTAINER) {
IPath containerPath = entry.getPath();
// skip JRE container, since it's already handled in VM_LOCATION.
if (!containerPath.toString().startsWith(JavaRuntime.JRE_CONTAINER)) {
IClasspathContainer container = JavaCore.getClasspathContainer(containerPath, javaProject);
if (container != null) {
entriesToBeScan.addAll(Arrays.asList(container.getClasspathEntries()));
}
}
continue;
} else if (entryKind == IClasspathEntry.CPE_LIBRARY) {
if (!path.toFile().exists()) {
// the case when lib path is relative
path = project.getFile(path.makeRelativeTo(project.getFullPath())).getLocation();
}
if (!path.toFile().exists()) {
continue;
}
}
Map<String, String> attributes = new HashMap<>();
for (IClasspathAttribute attribute : entry.getExtraAttributes()) {
attributes.put(attribute.getName(), attribute.getValue());
}
classpathEntries.add(new ProjectClasspathEntry(entry.getEntryKind(), path.toOSString(),
classpathEntries.add(new ProjectClasspathEntry(entryKind, path.toOSString(),
output == null ? null : output.toOSString(), attributes));
}
settings.putIfAbsent(key, classpathEntries);
Expand All @@ -175,6 +211,160 @@ public static Map<String, Object> getProjectSettings(String uri, List<String> se
return settings;
}

/**
* Update the project classpath by the given classpath entries.
*/
public static void updateClasspaths(String uri, List<ProjectClasspathEntry> entries, IProgressMonitor monitor) throws CoreException, URISyntaxException {
IJavaProject javaProject = getJavaProjectFromUri(uri);
IProject project = javaProject.getProject();
Map<IPath, IPath> sourceAndOutput = new HashMap<>();
List<IClasspathEntry> newEntries = new LinkedList<>();
List<IClasspathEntry> dependencyEntries = new LinkedList<>();
for (ProjectClasspathEntry entry : entries) {
if (entry.getKind() == IClasspathEntry.CPE_SOURCE) {
IPath path = project.getFolder(entry.getPath()).getFullPath();
IPath outputLocation = null;
String output = entry.getOutput();
if (output != null) {
if (".".equals(output)) {
outputLocation = project.getFullPath();
} else {
outputLocation = project.getFolder(output).getFullPath();
}
}
sourceAndOutput.put(path, outputLocation);
} else if (entry.getKind() == IClasspathEntry.CPE_CONTAINER) {
if (entry.getPath().startsWith(JavaRuntime.JRE_CONTAINER)) {
String jdkPath = entry.getPath().substring(JavaRuntime.JRE_CONTAINER.length());
newEntries.add(getNewJdkEntry(javaProject, jdkPath));
} else {
JavaLanguageServerPlugin.logInfo("The container entry " + entry.getPath() + " is not supported to be updated.");
}
} else {
dependencyEntries.add(convertClasspathEntry(entry));
}
}
IClasspathEntry[] sources = ProjectUtils.resolveSourceClasspathEntries(javaProject, sourceAndOutput, Collections.emptyList(), javaProject.getOutputLocation());
newEntries.addAll(Arrays.asList(sources));

// update maven classpath container since it's exposed from m2e.
// May consider extracting this operation to an API of IBuildSupport.
if (ProjectUtils.isMavenProject(project)) {
dependencyEntries = resolveMavenDependencyEntries(javaProject, dependencyEntries.toArray(IClasspathEntry[]::new), monitor);
}
newEntries.addAll(dependencyEntries);

IClasspathEntry[] rawClasspath = newEntries.toArray(IClasspathEntry[]::new);
IJavaModelStatus checkStatus = ClasspathEntry.validateClasspath(javaProject, rawClasspath, javaProject.getOutputLocation());
if (!checkStatus.isOK()) {
throw new CoreException(checkStatus);
}
javaProject.setRawClasspath(rawClasspath, monitor);
}

/**
* Convert ProjectClasspathEntry to IClasspathEntry.
*/
private static IClasspathEntry convertClasspathEntry(ProjectClasspathEntry entry) {
List<IClasspathAttribute> attributes = new LinkedList<>();
if (entry.getAttributes() != null) {
for (Entry<String, String> attributEntry : entry.getAttributes().entrySet()) {
attributes.add(JavaCore.newClasspathAttribute(attributEntry.getKey(), attributEntry.getValue()));
}
}
switch (entry.getKind()) {
case IClasspathEntry.CPE_CONTAINER:
return JavaCore.newContainerEntry(
IPath.fromOSString(entry.getPath()),
ClasspathEntry.NO_ACCESS_RULES,
attributes.toArray(IClasspathAttribute[]::new),
false
);
case IClasspathEntry.CPE_LIBRARY:
return JavaCore.newLibraryEntry(
IPath.fromOSString(entry.getPath()),
null,
null,
ClasspathEntry.NO_ACCESS_RULES,
attributes.toArray(IClasspathAttribute[]::new),
false
);
case IClasspathEntry.CPE_PROJECT:
return JavaCore.newProjectEntry(
IPath.fromOSString(entry.getPath()),
ClasspathEntry.NO_ACCESS_RULES,
false,
attributes.toArray(IClasspathAttribute[]::new),
false
);
default:
return null;
}
}

/**
* See {@link org.eclipse.m2e.jdt.internal.BuildPathManager#updateClasspath()}
*/
private static List<IClasspathEntry> resolveMavenDependencyEntries(IJavaProject javaProject, IClasspathEntry[] entries, IProgressMonitor monitor) {
IProject project = javaProject.getProject();
if (!ProjectUtils.isMavenProject(project)) {
JavaLanguageServerPlugin.logError("Project '" + javaProject.getElementName() + "' is not a Maven project.");
return Collections.emptyList();
}

List<IClasspathEntry> mavenEntries = new LinkedList<>();
List<IClasspathEntry> resolvedEntries = new LinkedList<>();
for (IClasspathEntry entry : entries) {
if (isMavenEntry(entry)) {
mavenEntries.add(entry);
} else {
resolvedEntries.add(entry);
}
}
try {
IClasspathEntry containerEntry = BuildPathManager.getMavenContainerEntry(javaProject);
IPath path = containerEntry != null ? containerEntry.getPath() :
IPath.fromOSString(BuildPathManager.CONTAINER_ID);
IClasspathContainer container = new MavenClasspathContainer(path, entries);
JavaCore.setClasspathContainer(container.getPath(), new IJavaProject[] {javaProject},
new IClasspathContainer[] {container}, monitor);
saveContainerState(project, container);
resolvedEntries.add(JavaCore.newContainerEntry(path));
} catch (JavaModelException e) {
JavaLanguageServerPlugin.log(e);
}
return resolvedEntries;
}

private static boolean isMavenEntry(IClasspathEntry entry) {
for (IClasspathAttribute attribute : entry.getExtraAttributes()) {
if (Objects.equals(IClasspathManager.POMDERIVED_ATTRIBUTE, attribute.getName()) && Boolean.parseBoolean(attribute.getValue())) {
return true;
}
}
return false;
}

/**
* See {@link org.eclipse.m2e.jdt.internal.BuildPathManager#saveContainerState()}
*/
private static void saveContainerState(IProject project, IClasspathContainer container) {
File containerStateFile = getContainerStateFile(project);
try (FileOutputStream is = new FileOutputStream(containerStateFile)) {
new MavenClasspathContainerSaveHelper().writeContainer(container, is);
} catch(IOException ex) {
JavaLanguageServerPlugin.logException("Can't save classpath container state for " + project.getName(), ex); //$NON-NLS-1$
}
}

/**
* See {@link org.eclipse.m2e.jdt.internal.BuildPathManager#getContainerStateFile()}
*/
private static File getContainerStateFile(IProject project) {
File stateLocationDir = MavenJdtPlugin.getDefault().getStateLocation().toFile();
return new File(stateLocationDir, project.getName() + ".container"); //$NON-NLS-1$
}

/**
* Updates the project source paths.
* @param uri Uri of the project.
Expand Down Expand Up @@ -432,31 +622,40 @@ public static SymbolInformation resolveWorkspaceSymbol(SymbolInformation request

public static JdkUpdateResult updateProjectJdk(String projectUri, String jdkPath, IProgressMonitor monitor) throws CoreException, URISyntaxException {
IJavaProject javaProject = ProjectCommand.getJavaProjectFromUri(projectUri);
IClasspathEntry[] originalClasspathEntries = javaProject.getRawClasspath();
IClasspathAttribute[] extraAttributes = null;
List<IClasspathEntry> newClasspathEntries = new ArrayList<>();
for (IClasspathEntry entry : originalClasspathEntries) {
if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER &&
entry.getPath().toString().startsWith("org.eclipse.jdt.launching.JRE_CONTAINER")) {
extraAttributes = entry.getExtraAttributes();
} else {
newClasspathEntries.add(entry);
try {
for (IClasspathEntry entry : javaProject.getRawClasspath()) {
if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER &&
entry.getPath().toString().startsWith(JavaRuntime.JRE_CONTAINER)) {
newClasspathEntries.add(getNewJdkEntry(javaProject, jdkPath));
} else {
newClasspathEntries.add(entry);
}
}
javaProject.setRawClasspath(newClasspathEntries.toArray(IClasspathEntry[]::new), monitor);
} catch (CoreException e) {
JavaLanguageServerPlugin.log(e);
return new JdkUpdateResult(false, e.getMessage());
}
return new JdkUpdateResult(true, jdkPath);
}

private static IClasspathEntry getNewJdkEntry(IJavaProject javaProject, String jdkPath) throws CoreException {
IVMInstall vmInstall = getVmInstallByPath(jdkPath);
List<IClasspathAttribute> extraAttributes = new ArrayList<>();
if (vmInstall == null) {
JavaLanguageServerPlugin.log(new Status(IStatus.ERROR, IConstants.PLUGIN_ID, "The select JDK path is not valid."));
return new JdkUpdateResult(false, "The selected JDK path is not valid.");
throw new CoreException(new Status(IStatus.ERROR, IConstants.PLUGIN_ID, "The select JDK path is not valid."));
}
newClasspathEntries.add(JavaCore.newContainerEntry(
if (javaProject.getOwnModuleDescription() != null) {
extraAttributes.add(JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, "true"));
}

return JavaCore.newContainerEntry(
JavaRuntime.newJREContainerPath(vmInstall),
ClasspathEntry.NO_ACCESS_RULES,
extraAttributes,
extraAttributes.toArray(IClasspathAttribute[]::new),
false /*isExported*/
));
javaProject.setRawClasspath(newClasspathEntries.toArray(IClasspathEntry[]::new), monitor);
return new JdkUpdateResult(true, vmInstall.getInstallLocation().getAbsolutePath());
);
}

private static IVMInstall getVmInstallByPath(String path) {
Expand Down
Loading

0 comments on commit 4c29a57

Please sign in to comment.