From 4c29a57cf5b408062d7d4b631904170bb96f0419 Mon Sep 17 00:00:00 2001 From: Sheng Chen Date: Mon, 18 Mar 2024 14:01:04 +0800 Subject: [PATCH 1/2] Support updating whole classpath of the project Signed-off-by: Sheng Chen --- .../internal/JDTDelegateCommandHandler.java | 8 +- .../jdt/ls/core/internal/ProjectUtils.java | 35 ++- .../internal/commands/ProjectCommand.java | 237 ++++++++++++++++-- .../internal/commands/ProjectCommandTest.java | 23 ++ 4 files changed, 266 insertions(+), 37 deletions(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java index 8c838fe720..653225dd2a 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java @@ -102,13 +102,7 @@ public Object executeCommand(String commandId, List arguments, IProgress case "java.project.updateClassPaths": { String projectUri = (String) arguments.get(0); ProjectClasspathEntries entries = (JSONUtility.toModel(arguments.get(1), ProjectClasspathEntries.class)); - Map 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": diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ProjectUtils.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ProjectUtils.java index 3a2de147d8..97d88efcc7 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ProjectUtils.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ProjectUtils.java @@ -700,11 +700,32 @@ public static void refreshDiagnostics(IProgressMonitor monitor) throws JavaModel */ public static IClasspathEntry[] resolveClassPathEntries(IJavaProject javaProject, Map sourceAndOutput, List excludingPaths, IPath outputPath) throws CoreException { List newEntries = new LinkedList<>(); - Map 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 sourceAndOutput, List excludingPaths, IPath outputPath) throws CoreException { + Map originalSources = new HashMap<>(); + for (IClasspathEntry entry : javaProject.getRawClasspath()) { + if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { originalSources.put(entry.getPath(), entry); } } @@ -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); } } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommand.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommand.java index c82723316e..4ca644a8ed 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommand.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommand.java @@ -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; @@ -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; @@ -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; @@ -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 { @@ -147,22 +158,47 @@ public static Map getProjectSettings(String uri, List se settings.putIfAbsent(key, referencedLibraries); break; case CLASSPATH_ENTRIES: - IClasspathEntry[] entries = javaProject.getRawClasspath(); + List entriesToBeScan = new LinkedList<>(); + Collections.addAll(entriesToBeScan, javaProject.getRawClasspath()); List 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 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); @@ -175,6 +211,160 @@ public static Map getProjectSettings(String uri, List se return settings; } + /** + * Update the project classpath by the given classpath entries. + */ + public static void updateClasspaths(String uri, List entries, IProgressMonitor monitor) throws CoreException, URISyntaxException { + IJavaProject javaProject = getJavaProjectFromUri(uri); + IProject project = javaProject.getProject(); + Map sourceAndOutput = new HashMap<>(); + List newEntries = new LinkedList<>(); + List 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 attributes = new LinkedList<>(); + if (entry.getAttributes() != null) { + for (Entry 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 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 mavenEntries = new LinkedList<>(); + List 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. @@ -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 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 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) { diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommandTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommandTest.java index a986cdbcc5..b353269a96 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommandTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommandTest.java @@ -173,6 +173,29 @@ public void testGetClasspathEntries() throws Exception { List entries = (List) options.get(ProjectCommand.CLASSPATH_ENTRIES); assertNotNull(options.get(ProjectCommand.CLASSPATH_ENTRIES)); assertTrue(entries.size() > 0); + assertTrue(entries.stream().anyMatch(entry -> { + return entry.getKind() == IClasspathEntry.CPE_SOURCE; + })); + assertTrue(entries.stream().anyMatch(entry -> { + return entry.getKind() == IClasspathEntry.CPE_LIBRARY; + })); + } + + @Test + public void testUpdateClasspathEntries() throws Exception { + importProjects("maven/salut2"); + IProject project = WorkspaceHelper.getProject("salut2"); + String uriString = project.getFile("src/main/java/foo/Bar.java").getLocationURI().toString(); + List settingKeys = Arrays.asList(ProjectCommand.CLASSPATH_ENTRIES); + Map options = ProjectCommand.getProjectSettings(uriString, settingKeys); + List entries = (List) options.get(ProjectCommand.CLASSPATH_ENTRIES); + + int size = entries.size(); + entries.remove(size - 1); + ProjectCommand.updateClasspaths(uriString, entries, new NullProgressMonitor()); + + List newEntries = (List) options.get(ProjectCommand.CLASSPATH_ENTRIES); + assertEquals(size - 1, newEntries.size()); } @Test From 4f8b2f148f2c9a6f366cf7efd22c4721a91054fa Mon Sep 17 00:00:00 2001 From: Sheng Chen Date: Wed, 20 Mar 2024 11:27:37 +0800 Subject: [PATCH 2/2] Address comments Signed-off-by: Sheng Chen --- .../internal/commands/ProjectCommand.java | 168 +++++++++--------- .../internal/commands/ProjectCommandTest.java | 1 + 2 files changed, 81 insertions(+), 88 deletions(-) diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommand.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommand.java index 4ca644a8ed..9448543699 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommand.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommand.java @@ -14,8 +14,6 @@ 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; @@ -31,7 +29,6 @@ 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; @@ -77,11 +74,6 @@ 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 { @@ -176,13 +168,9 @@ public static Map getProjectSettings(String uri, List se 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())); - } + if (!path.toString().startsWith(JavaRuntime.JRE_CONTAINER)) { + entriesToBeScan.addAll(expandContainerEntry(javaProject, entry)); } continue; } else if (entryKind == IClasspathEntry.CPE_LIBRARY) { @@ -219,7 +207,7 @@ public static void updateClasspaths(String uri, List entr IProject project = javaProject.getProject(); Map sourceAndOutput = new HashMap<>(); List newEntries = new LinkedList<>(); - List dependencyEntries = new LinkedList<>(); + List newDependencyEntries = new LinkedList<>(); for (ProjectClasspathEntry entry : entries) { if (entry.getKind() == IClasspathEntry.CPE_SOURCE) { IPath path = project.getFolder(entry.getPath()).getFullPath(); @@ -241,18 +229,12 @@ public static void updateClasspaths(String uri, List entr JavaLanguageServerPlugin.logInfo("The container entry " + entry.getPath() + " is not supported to be updated."); } } else { - dependencyEntries.add(convertClasspathEntry(entry)); + newDependencyEntries.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); + newEntries.addAll(resolveDependencyEntries(javaProject, newDependencyEntries)); IClasspathEntry[] rawClasspath = newEntries.toArray(IClasspathEntry[]::new); IJavaModelStatus checkStatus = ClasspathEntry.validateClasspath(javaProject, rawClasspath, javaProject.getOutputLocation()); @@ -262,14 +244,87 @@ public static void updateClasspaths(String uri, List entr javaProject.setRawClasspath(rawClasspath, monitor); } + /** + * Check the new dependency entries are different from the current ones or not. + * If they are equal, return the current dependency entries, otherwise return the new ones. + */ + private static List resolveDependencyEntries(IJavaProject javaProject, List newEntries) throws JavaModelException { + List currentDependencyEntries = new LinkedList<>(); + for (IClasspathEntry entry : javaProject.getRawClasspath()) { + if (entry.getEntryKind() != IClasspathEntry.CPE_SOURCE && + !entry.getPath().toString().startsWith(JavaRuntime.JRE_CONTAINER)) { + currentDependencyEntries.add(entry); + } + } + + Map currentEntryMapping = new HashMap<>(); + for (IClasspathEntry entry : currentDependencyEntries) { + if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { + List expandedContainerEntry = expandContainerEntry(javaProject, entry); + for (IClasspathEntry containerEntry : expandedContainerEntry) { + currentEntryMapping.put(containerEntry.getPath(), containerEntry); + } + } else { + currentEntryMapping.put(entry.getPath(), entry); + } + } + + // Use new dependency entries if the size is different + if (newEntries.size() != currentEntryMapping.size()) { + return newEntries; + } + + for (IClasspathEntry entry : newEntries) { + IClasspathEntry currentEntry = currentEntryMapping.get(entry.getPath()); + if (currentEntry == null) { + return newEntries; + } + } + + return currentDependencyEntries; + } + + /** + * Expand the container entry, the returned list is guaranteed to contain no container entry. + */ + private static List expandContainerEntry(IJavaProject javaProject, IClasspathEntry entry) throws JavaModelException { + if (entry.getEntryKind() != IClasspathEntry.CPE_CONTAINER) { + return Collections.singletonList(entry); + } + + List resolvedEntries = new LinkedList<>(); + List entriesToScan = new LinkedList<>(); + entriesToScan.add(entry); + for (int i = 0; i < entriesToScan.size(); i++) { + IClasspathEntry currentEntry = entriesToScan.get(i); + if (currentEntry.getEntryKind() != IClasspathEntry.CPE_CONTAINER) { + resolvedEntries.add(currentEntry); + continue; + } + IClasspathContainer container = JavaCore.getClasspathContainer(currentEntry.getPath(), javaProject); + if (container == null) { + continue; + } + IClasspathEntry[] containerEntries = container.getClasspathEntries(); + for (IClasspathEntry containerEntry : containerEntries) { + if (containerEntry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { + entriesToScan.add(containerEntry); + } else { + resolvedEntries.add(containerEntry); + } + } + } + return resolvedEntries; + } + /** * Convert ProjectClasspathEntry to IClasspathEntry. */ private static IClasspathEntry convertClasspathEntry(ProjectClasspathEntry entry) { List attributes = new LinkedList<>(); if (entry.getAttributes() != null) { - for (Entry attributEntry : entry.getAttributes().entrySet()) { - attributes.add(JavaCore.newClasspathAttribute(attributEntry.getKey(), attributEntry.getValue())); + for (Entry attributeEntry : entry.getAttributes().entrySet()) { + attributes.add(JavaCore.newClasspathAttribute(attributeEntry.getKey(), attributeEntry.getValue())); } } switch (entry.getKind()) { @@ -302,69 +357,6 @@ private static IClasspathEntry convertClasspathEntry(ProjectClasspathEntry entry } } - /** - * See {@link org.eclipse.m2e.jdt.internal.BuildPathManager#updateClasspath()} - */ - private static List 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 mavenEntries = new LinkedList<>(); - List 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. diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommandTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommandTest.java index b353269a96..187ecaf6f4 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommandTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommandTest.java @@ -194,6 +194,7 @@ public void testUpdateClasspathEntries() throws Exception { entries.remove(size - 1); ProjectCommand.updateClasspaths(uriString, entries, new NullProgressMonitor()); + options = ProjectCommand.getProjectSettings(uriString, settingKeys); List newEntries = (List) options.get(ProjectCommand.CLASSPATH_ENTRIES); assertEquals(size - 1, newEntries.size()); }