Skip to content

Commit 4c29a57

Browse files
committed
Support updating whole classpath of the project
Signed-off-by: Sheng Chen <[email protected]>
1 parent 5367708 commit 4c29a57

File tree

4 files changed

+266
-37
lines changed

4 files changed

+266
-37
lines changed

org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java

+1-7
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,7 @@ public Object executeCommand(String commandId, List<Object> arguments, IProgress
102102
case "java.project.updateClassPaths": {
103103
String projectUri = (String) arguments.get(0);
104104
ProjectClasspathEntries entries = (JSONUtility.toModel(arguments.get(1), ProjectClasspathEntries.class));
105-
Map<String, String> sourceAndOutput = new HashMap<>();
106-
for (ProjectClasspathEntry entry : entries.getClasspathEntries()) {
107-
if (entry.getKind() == IClasspathEntry.CPE_SOURCE) {
108-
sourceAndOutput.put(entry.getPath(), entry.getOutput());
109-
}
110-
}
111-
ProjectCommand.updateSourcePaths(projectUri, sourceAndOutput);
105+
ProjectCommand.updateClasspaths(projectUri, entries.getClasspathEntries(), monitor);
112106
return null;
113107
}
114108
case "java.project.isTestFile":

org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ProjectUtils.java

+24-11
Original file line numberDiff line numberDiff line change
@@ -700,11 +700,32 @@ public static void refreshDiagnostics(IProgressMonitor monitor) throws JavaModel
700700
*/
701701
public static IClasspathEntry[] resolveClassPathEntries(IJavaProject javaProject, Map<IPath, IPath> sourceAndOutput, List<IPath> excludingPaths, IPath outputPath) throws CoreException {
702702
List<IClasspathEntry> newEntries = new LinkedList<>();
703-
Map<IPath, IClasspathEntry> originalSources = new HashMap<>();
704703
for (IClasspathEntry entry : javaProject.getRawClasspath()) {
705704
if (entry.getEntryKind() != IClasspathEntry.CPE_SOURCE) {
706705
newEntries.add(entry);
707-
} else {
706+
}
707+
}
708+
709+
if (outputPath == null) {
710+
outputPath = javaProject.getOutputLocation();
711+
}
712+
713+
IClasspathEntry[] newSources = resolveSourceClasspathEntries(javaProject, sourceAndOutput, excludingPaths, outputPath);
714+
newEntries.addAll(Arrays.asList(newSources));
715+
716+
IClasspathEntry[] rawClasspath = newEntries.toArray(IClasspathEntry[]::new);
717+
IJavaModelStatus checkStatus = ClasspathEntry.validateClasspath(javaProject, rawClasspath, outputPath);
718+
if (!checkStatus.isOK()) {
719+
throw new CoreException(checkStatus);
720+
}
721+
722+
return rawClasspath;
723+
}
724+
725+
public static IClasspathEntry[] resolveSourceClasspathEntries(IJavaProject javaProject, Map<IPath, IPath> sourceAndOutput, List<IPath> excludingPaths, IPath outputPath) throws CoreException {
726+
Map<IPath, IClasspathEntry> originalSources = new HashMap<>();
727+
for (IClasspathEntry entry : javaProject.getRawClasspath()) {
728+
if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
708729
originalSources.put(entry.getPath(), entry);
709730
}
710731
}
@@ -757,15 +778,7 @@ public static IClasspathEntry[] resolveClassPathEntries(IJavaProject javaProject
757778
}
758779
}
759780
}
760-
newEntries.addAll(sourceEntries);
761-
762-
IClasspathEntry[] rawClasspath = newEntries.toArray(IClasspathEntry[]::new);
763-
IJavaModelStatus checkStatus = ClasspathEntry.validateClasspath(javaProject, rawClasspath, outputPath);
764-
if (!checkStatus.isOK()) {
765-
throw new CoreException(checkStatus);
766-
}
767-
768-
return rawClasspath;
781+
return sourceEntries.toArray(IClasspathEntry[]::new);
769782
}
770783

771784
}

org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommand.java

+218-19
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
package org.eclipse.jdt.ls.core.internal.commands;
1515

1616
import java.io.File;
17+
import java.io.FileOutputStream;
18+
import java.io.IOException;
1719
import java.net.URI;
1820
import java.net.URISyntaxException;
1921
import java.util.ArrayDeque;
@@ -28,6 +30,8 @@
2830
import java.util.List;
2931
import java.util.Map;
3032
import java.util.Optional;
33+
import java.util.Map.Entry;
34+
import java.util.Objects;
3135
import java.util.stream.Stream;
3236

3337
import org.eclipse.core.resources.IContainer;
@@ -46,9 +50,11 @@
4650
import org.eclipse.core.runtime.jobs.Job;
4751
import org.eclipse.debug.core.ILaunchConfiguration;
4852
import org.eclipse.jdt.core.IClasspathAttribute;
53+
import org.eclipse.jdt.core.IClasspathContainer;
4954
import org.eclipse.jdt.core.IClasspathEntry;
5055
import org.eclipse.jdt.core.ICompilationUnit;
5156
import org.eclipse.jdt.core.IJavaElement;
57+
import org.eclipse.jdt.core.IJavaModelStatus;
5258
import org.eclipse.jdt.core.IJavaProject;
5359
import org.eclipse.jdt.core.IParent;
5460
import org.eclipse.jdt.core.IType;
@@ -71,6 +77,11 @@
7177
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
7278
import org.eclipse.lsp4j.Location;
7379
import org.eclipse.lsp4j.SymbolInformation;
80+
import org.eclipse.m2e.jdt.IClasspathManager;
81+
import org.eclipse.m2e.jdt.MavenJdtPlugin;
82+
import org.eclipse.m2e.jdt.internal.BuildPathManager;
83+
import org.eclipse.m2e.jdt.internal.MavenClasspathContainer;
84+
import org.eclipse.m2e.jdt.internal.MavenClasspathContainerSaveHelper;
7485

7586
public class ProjectCommand {
7687

@@ -147,22 +158,47 @@ public static Map<String, Object> getProjectSettings(String uri, List<String> se
147158
settings.putIfAbsent(key, referencedLibraries);
148159
break;
149160
case CLASSPATH_ENTRIES:
150-
IClasspathEntry[] entries = javaProject.getRawClasspath();
161+
List<IClasspathEntry> entriesToBeScan = new LinkedList<>();
162+
Collections.addAll(entriesToBeScan, javaProject.getRawClasspath());
151163
List<ProjectClasspathEntry> classpathEntries = new LinkedList<>();
152-
for (IClasspathEntry entry : entries) {
164+
for (int i = 0; i < entriesToBeScan.size(); i++) {
165+
IClasspathEntry entry = entriesToBeScan.get(i);
153166
IPath path = entry.getPath();
154167
IPath output = entry.getOutputLocation();
155-
if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
168+
int entryKind = entry.getEntryKind();
169+
if (entryKind == IClasspathEntry.CPE_SOURCE) {
170+
IPath relativePath = path.makeRelativeTo(project.getFullPath());
171+
if (relativePath.isEmpty()) {
172+
continue; // A valid relative source path should not be empty.
173+
}
156174
path = project.getFolder(path.makeRelativeTo(project.getFullPath())).getLocation();
157175
if (output != null) {
158176
output = project.getFolder(output.makeRelativeTo(project.getFullPath())).getLocation();
159177
}
178+
} else if (entryKind == IClasspathEntry.CPE_CONTAINER) {
179+
IPath containerPath = entry.getPath();
180+
// skip JRE container, since it's already handled in VM_LOCATION.
181+
if (!containerPath.toString().startsWith(JavaRuntime.JRE_CONTAINER)) {
182+
IClasspathContainer container = JavaCore.getClasspathContainer(containerPath, javaProject);
183+
if (container != null) {
184+
entriesToBeScan.addAll(Arrays.asList(container.getClasspathEntries()));
185+
}
186+
}
187+
continue;
188+
} else if (entryKind == IClasspathEntry.CPE_LIBRARY) {
189+
if (!path.toFile().exists()) {
190+
// the case when lib path is relative
191+
path = project.getFile(path.makeRelativeTo(project.getFullPath())).getLocation();
192+
}
193+
if (!path.toFile().exists()) {
194+
continue;
195+
}
160196
}
161197
Map<String, String> attributes = new HashMap<>();
162198
for (IClasspathAttribute attribute : entry.getExtraAttributes()) {
163199
attributes.put(attribute.getName(), attribute.getValue());
164200
}
165-
classpathEntries.add(new ProjectClasspathEntry(entry.getEntryKind(), path.toOSString(),
201+
classpathEntries.add(new ProjectClasspathEntry(entryKind, path.toOSString(),
166202
output == null ? null : output.toOSString(), attributes));
167203
}
168204
settings.putIfAbsent(key, classpathEntries);
@@ -175,6 +211,160 @@ public static Map<String, Object> getProjectSettings(String uri, List<String> se
175211
return settings;
176212
}
177213

214+
/**
215+
* Update the project classpath by the given classpath entries.
216+
*/
217+
public static void updateClasspaths(String uri, List<ProjectClasspathEntry> entries, IProgressMonitor monitor) throws CoreException, URISyntaxException {
218+
IJavaProject javaProject = getJavaProjectFromUri(uri);
219+
IProject project = javaProject.getProject();
220+
Map<IPath, IPath> sourceAndOutput = new HashMap<>();
221+
List<IClasspathEntry> newEntries = new LinkedList<>();
222+
List<IClasspathEntry> dependencyEntries = new LinkedList<>();
223+
for (ProjectClasspathEntry entry : entries) {
224+
if (entry.getKind() == IClasspathEntry.CPE_SOURCE) {
225+
IPath path = project.getFolder(entry.getPath()).getFullPath();
226+
IPath outputLocation = null;
227+
String output = entry.getOutput();
228+
if (output != null) {
229+
if (".".equals(output)) {
230+
outputLocation = project.getFullPath();
231+
} else {
232+
outputLocation = project.getFolder(output).getFullPath();
233+
}
234+
}
235+
sourceAndOutput.put(path, outputLocation);
236+
} else if (entry.getKind() == IClasspathEntry.CPE_CONTAINER) {
237+
if (entry.getPath().startsWith(JavaRuntime.JRE_CONTAINER)) {
238+
String jdkPath = entry.getPath().substring(JavaRuntime.JRE_CONTAINER.length());
239+
newEntries.add(getNewJdkEntry(javaProject, jdkPath));
240+
} else {
241+
JavaLanguageServerPlugin.logInfo("The container entry " + entry.getPath() + " is not supported to be updated.");
242+
}
243+
} else {
244+
dependencyEntries.add(convertClasspathEntry(entry));
245+
}
246+
}
247+
IClasspathEntry[] sources = ProjectUtils.resolveSourceClasspathEntries(javaProject, sourceAndOutput, Collections.emptyList(), javaProject.getOutputLocation());
248+
newEntries.addAll(Arrays.asList(sources));
249+
250+
// update maven classpath container since it's exposed from m2e.
251+
// May consider extracting this operation to an API of IBuildSupport.
252+
if (ProjectUtils.isMavenProject(project)) {
253+
dependencyEntries = resolveMavenDependencyEntries(javaProject, dependencyEntries.toArray(IClasspathEntry[]::new), monitor);
254+
}
255+
newEntries.addAll(dependencyEntries);
256+
257+
IClasspathEntry[] rawClasspath = newEntries.toArray(IClasspathEntry[]::new);
258+
IJavaModelStatus checkStatus = ClasspathEntry.validateClasspath(javaProject, rawClasspath, javaProject.getOutputLocation());
259+
if (!checkStatus.isOK()) {
260+
throw new CoreException(checkStatus);
261+
}
262+
javaProject.setRawClasspath(rawClasspath, monitor);
263+
}
264+
265+
/**
266+
* Convert ProjectClasspathEntry to IClasspathEntry.
267+
*/
268+
private static IClasspathEntry convertClasspathEntry(ProjectClasspathEntry entry) {
269+
List<IClasspathAttribute> attributes = new LinkedList<>();
270+
if (entry.getAttributes() != null) {
271+
for (Entry<String, String> attributEntry : entry.getAttributes().entrySet()) {
272+
attributes.add(JavaCore.newClasspathAttribute(attributEntry.getKey(), attributEntry.getValue()));
273+
}
274+
}
275+
switch (entry.getKind()) {
276+
case IClasspathEntry.CPE_CONTAINER:
277+
return JavaCore.newContainerEntry(
278+
IPath.fromOSString(entry.getPath()),
279+
ClasspathEntry.NO_ACCESS_RULES,
280+
attributes.toArray(IClasspathAttribute[]::new),
281+
false
282+
);
283+
case IClasspathEntry.CPE_LIBRARY:
284+
return JavaCore.newLibraryEntry(
285+
IPath.fromOSString(entry.getPath()),
286+
null,
287+
null,
288+
ClasspathEntry.NO_ACCESS_RULES,
289+
attributes.toArray(IClasspathAttribute[]::new),
290+
false
291+
);
292+
case IClasspathEntry.CPE_PROJECT:
293+
return JavaCore.newProjectEntry(
294+
IPath.fromOSString(entry.getPath()),
295+
ClasspathEntry.NO_ACCESS_RULES,
296+
false,
297+
attributes.toArray(IClasspathAttribute[]::new),
298+
false
299+
);
300+
default:
301+
return null;
302+
}
303+
}
304+
305+
/**
306+
* See {@link org.eclipse.m2e.jdt.internal.BuildPathManager#updateClasspath()}
307+
*/
308+
private static List<IClasspathEntry> resolveMavenDependencyEntries(IJavaProject javaProject, IClasspathEntry[] entries, IProgressMonitor monitor) {
309+
IProject project = javaProject.getProject();
310+
if (!ProjectUtils.isMavenProject(project)) {
311+
JavaLanguageServerPlugin.logError("Project '" + javaProject.getElementName() + "' is not a Maven project.");
312+
return Collections.emptyList();
313+
}
314+
315+
List<IClasspathEntry> mavenEntries = new LinkedList<>();
316+
List<IClasspathEntry> resolvedEntries = new LinkedList<>();
317+
for (IClasspathEntry entry : entries) {
318+
if (isMavenEntry(entry)) {
319+
mavenEntries.add(entry);
320+
} else {
321+
resolvedEntries.add(entry);
322+
}
323+
}
324+
try {
325+
IClasspathEntry containerEntry = BuildPathManager.getMavenContainerEntry(javaProject);
326+
IPath path = containerEntry != null ? containerEntry.getPath() :
327+
IPath.fromOSString(BuildPathManager.CONTAINER_ID);
328+
IClasspathContainer container = new MavenClasspathContainer(path, entries);
329+
JavaCore.setClasspathContainer(container.getPath(), new IJavaProject[] {javaProject},
330+
new IClasspathContainer[] {container}, monitor);
331+
saveContainerState(project, container);
332+
resolvedEntries.add(JavaCore.newContainerEntry(path));
333+
} catch (JavaModelException e) {
334+
JavaLanguageServerPlugin.log(e);
335+
}
336+
return resolvedEntries;
337+
}
338+
339+
private static boolean isMavenEntry(IClasspathEntry entry) {
340+
for (IClasspathAttribute attribute : entry.getExtraAttributes()) {
341+
if (Objects.equals(IClasspathManager.POMDERIVED_ATTRIBUTE, attribute.getName()) && Boolean.parseBoolean(attribute.getValue())) {
342+
return true;
343+
}
344+
}
345+
return false;
346+
}
347+
348+
/**
349+
* See {@link org.eclipse.m2e.jdt.internal.BuildPathManager#saveContainerState()}
350+
*/
351+
private static void saveContainerState(IProject project, IClasspathContainer container) {
352+
File containerStateFile = getContainerStateFile(project);
353+
try (FileOutputStream is = new FileOutputStream(containerStateFile)) {
354+
new MavenClasspathContainerSaveHelper().writeContainer(container, is);
355+
} catch(IOException ex) {
356+
JavaLanguageServerPlugin.logException("Can't save classpath container state for " + project.getName(), ex); //$NON-NLS-1$
357+
}
358+
}
359+
360+
/**
361+
* See {@link org.eclipse.m2e.jdt.internal.BuildPathManager#getContainerStateFile()}
362+
*/
363+
private static File getContainerStateFile(IProject project) {
364+
File stateLocationDir = MavenJdtPlugin.getDefault().getStateLocation().toFile();
365+
return new File(stateLocationDir, project.getName() + ".container"); //$NON-NLS-1$
366+
}
367+
178368
/**
179369
* Updates the project source paths.
180370
* @param uri Uri of the project.
@@ -432,31 +622,40 @@ public static SymbolInformation resolveWorkspaceSymbol(SymbolInformation request
432622

433623
public static JdkUpdateResult updateProjectJdk(String projectUri, String jdkPath, IProgressMonitor monitor) throws CoreException, URISyntaxException {
434624
IJavaProject javaProject = ProjectCommand.getJavaProjectFromUri(projectUri);
435-
IClasspathEntry[] originalClasspathEntries = javaProject.getRawClasspath();
436-
IClasspathAttribute[] extraAttributes = null;
437625
List<IClasspathEntry> newClasspathEntries = new ArrayList<>();
438-
for (IClasspathEntry entry : originalClasspathEntries) {
439-
if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER &&
440-
entry.getPath().toString().startsWith("org.eclipse.jdt.launching.JRE_CONTAINER")) {
441-
extraAttributes = entry.getExtraAttributes();
442-
} else {
443-
newClasspathEntries.add(entry);
626+
try {
627+
for (IClasspathEntry entry : javaProject.getRawClasspath()) {
628+
if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER &&
629+
entry.getPath().toString().startsWith(JavaRuntime.JRE_CONTAINER)) {
630+
newClasspathEntries.add(getNewJdkEntry(javaProject, jdkPath));
631+
} else {
632+
newClasspathEntries.add(entry);
633+
}
444634
}
635+
javaProject.setRawClasspath(newClasspathEntries.toArray(IClasspathEntry[]::new), monitor);
636+
} catch (CoreException e) {
637+
JavaLanguageServerPlugin.log(e);
638+
return new JdkUpdateResult(false, e.getMessage());
445639
}
640+
return new JdkUpdateResult(true, jdkPath);
641+
}
446642

643+
private static IClasspathEntry getNewJdkEntry(IJavaProject javaProject, String jdkPath) throws CoreException {
447644
IVMInstall vmInstall = getVmInstallByPath(jdkPath);
645+
List<IClasspathAttribute> extraAttributes = new ArrayList<>();
448646
if (vmInstall == null) {
449-
JavaLanguageServerPlugin.log(new Status(IStatus.ERROR, IConstants.PLUGIN_ID, "The select JDK path is not valid."));
450-
return new JdkUpdateResult(false, "The selected JDK path is not valid.");
647+
throw new CoreException(new Status(IStatus.ERROR, IConstants.PLUGIN_ID, "The select JDK path is not valid."));
451648
}
452-
newClasspathEntries.add(JavaCore.newContainerEntry(
649+
if (javaProject.getOwnModuleDescription() != null) {
650+
extraAttributes.add(JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, "true"));
651+
}
652+
653+
return JavaCore.newContainerEntry(
453654
JavaRuntime.newJREContainerPath(vmInstall),
454655
ClasspathEntry.NO_ACCESS_RULES,
455-
extraAttributes,
656+
extraAttributes.toArray(IClasspathAttribute[]::new),
456657
false /*isExported*/
457-
));
458-
javaProject.setRawClasspath(newClasspathEntries.toArray(IClasspathEntry[]::new), monitor);
459-
return new JdkUpdateResult(true, vmInstall.getInstallLocation().getAbsolutePath());
658+
);
460659
}
461660

462661
private static IVMInstall getVmInstallByPath(String path) {

0 commit comments

Comments
 (0)