Skip to content

Commit

Permalink
Support add/remove imported projects
Browse files Browse the repository at this point in the history
Signed-off-by: Sheng Chen <[email protected]>
  • Loading branch information
jdneo committed Nov 29, 2023
1 parent 58fc1d6 commit 57847f4
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 10 deletions.
3 changes: 3 additions & 0 deletions org.eclipse.jdt.ls.core/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@
<command
id="java.project.import">
</command>
<command
id="java.project.changeImportedProjects">
</command>
<command
id="java.project.resolveStackTraceLocation">
</command>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public enum EventType {
*/
ProjectsImported(200),

ProjectsDeleted(210),

/**
* Incompatible issue between Gradle and Jdk event.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.eclipse.jdt.ls.core.internal.commands.OrganizeImportsCommand;
import org.eclipse.jdt.ls.core.internal.commands.ProjectCommand;
import org.eclipse.jdt.ls.core.internal.commands.ProjectCommand.ClasspathOptions;
import org.eclipse.jdt.ls.core.internal.commands.ProjectCommand.GetAllProjectOptions;
import org.eclipse.jdt.ls.core.internal.commands.SourceAttachmentCommand;
import org.eclipse.jdt.ls.core.internal.commands.TypeHierarchyCommand;
import org.eclipse.jdt.ls.core.internal.framework.protobuf.ProtobufSupport;
Expand Down Expand Up @@ -96,6 +97,12 @@ public Object executeCommand(String commandId, List<Object> arguments, IProgress
case "java.project.isTestFile":
return ProjectCommand.isTestFile((String) arguments.get(0));
case "java.project.getAll":
if (!arguments.isEmpty()) {
GetAllProjectOptions option = JSONUtility.toModel(arguments.get(0), GetAllProjectOptions.class);
if (option.includeNonJava) {
return ProjectCommand.getAllProjects();
}
}
return ProjectCommand.getAllJavaProjects();
case "java.project.refreshDiagnostics":
if (arguments.size() < 4) {
Expand All @@ -105,6 +112,10 @@ public Object executeCommand(String commandId, List<Object> arguments, IProgress
case "java.project.import":
ProjectCommand.importProject(monitor);
return null;
case "java.project.changeImportedProjects":
ProjectCommand.changeImportedProjects((ArrayList<String>) arguments.get(0),
(ArrayList<String>) arguments.get(1), (ArrayList<String>) arguments.get(2), monitor);
return null;
case "java.project.resolveStackTraceLocation":
List<String> projectNames = null;
if (arguments.size() > 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
Expand Down Expand Up @@ -170,12 +171,9 @@ public static ClasspathResult getClasspathsFromJavaProject(IJavaProject javaProj
} else {
schedulingRule = javaProject.getSchedulingRule();
}
workspace.run(new IWorkspaceRunnable() {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
String[][] paths = delegate.getClasspathAndModulepath(launchConfig);
result[0] = new ClasspathResult(javaProject.getProject().getLocationURI(), paths[0], paths[1]);
}
workspace.run((IWorkspaceRunnable) monitor -> {
String[][] paths = delegate.getClasspathAndModulepath(launchConfig);
result[0] = new ClasspathResult(javaProject.getProject().getLocationURI(), paths[0], paths[1]);
}, schedulingRule, IWorkspace.AVOID_UPDATE, new NullProgressMonitor());

if (result[0] != null) {
Expand Down Expand Up @@ -217,17 +215,31 @@ public static boolean isTestFile(String uri) throws CoreException {
}

public static List<URI> getAllJavaProjects() {
List<URI> javaProjects = new LinkedList<>();
for (IJavaProject javaProject : ProjectUtils.getJavaProjects()) {
javaProjects.add(ProjectUtils.getProjectRealFolder(javaProject.getProject()).toFile().toURI());
return getProjectUris(Arrays.stream(ProjectUtils.getJavaProjects())
.map(IJavaProject::getProject).toArray(IProject[]::new));
}

public static List<URI> getAllProjects() {
return getProjectUris(ProjectUtils.getAllProjects());
}

private static List<URI> getProjectUris(IProject[] projects) {
List<URI> projectUris = new LinkedList<>();
for (IProject project : projects) {
projectUris.add(ProjectUtils.getProjectRealFolder(project).toFile().toURI());
}
return javaProjects;
return projectUris;
}

public static void importProject(IProgressMonitor monitor) {
JavaLanguageServerPlugin.getProjectsManager().importProjects(monitor);
}

public static void changeImportedProjects(Collection<String> toUpdate, Collection<String> toImport,
Collection<String> toDelete, IProgressMonitor monitor) {
JavaLanguageServerPlugin.getProjectsManager().changeImportedProjects(toUpdate, toImport, toDelete, monitor);
}

private static IPath[] listTestSourcePaths(IJavaProject project) throws JavaModelException {
List<IPath> result = new ArrayList<>();
for (IClasspathEntry entry : project.getRawClasspath()) {
Expand Down Expand Up @@ -314,6 +326,10 @@ public ClasspathResult(URI projectRoot, String[] classpaths, String[] modulepath
}
}

public static class GetAllProjectOptions {
public boolean includeNonJava;
}

public static SymbolInformation resolveWorkspaceSymbol(SymbolInformation request) {
ITypeRoot unit = JDTUtils.resolveTypeRoot(request.getLocation().getUri());
if (unit == null || !unit.exists()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
Expand All @@ -35,6 +38,8 @@
import org.eclipse.core.internal.resources.CharsetManager;
import org.eclipse.core.internal.resources.Workspace;
import org.eclipse.core.resources.FileInfoMatcherDescription;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
Expand Down Expand Up @@ -73,6 +78,7 @@
import org.eclipse.jdt.ls.core.internal.IConstants;
import org.eclipse.jdt.ls.core.internal.IProjectImporter;
import org.eclipse.jdt.ls.core.internal.JDTEnvironmentUtils;
import org.eclipse.jdt.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.JavaClientConnection.JavaLanguageClient;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.JobHelpers;
Expand All @@ -83,6 +89,7 @@
import org.eclipse.jdt.ls.core.internal.handlers.BaseInitHandler;
import org.eclipse.jdt.ls.core.internal.handlers.ProjectEncodingMode;
import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager;
import org.eclipse.lsp4j.PublishDiagnosticsParams;

public abstract class ProjectsManager implements ISaveParticipant, IProjectsManager {

Expand Down Expand Up @@ -233,6 +240,42 @@ public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
job.schedule();
}

public void changeImportedProjects(Collection<String> toImport, Collection<String> toUpdate,
Collection<String> toDelete, IProgressMonitor monitor) {
Set<IPath> filesToImport = new HashSet<>();
for (String uri : toImport) {
filesToImport.add(ResourceUtils.canonicalFilePathFromURI(uri));
}

Set<IProject> projectsToUpdate = new HashSet<>();
for (String uri : toUpdate) {
IContainer folder = JDTUtils.findFolder(uri);
if (folder == null) {
continue;
}
IProject project = folder.getProject();
if (project != null) {
projectsToUpdate.add(project);
}
}

Set<IProject> projectsToDelete = new HashSet<>();
for (String uri : toDelete) {
IContainer folder = JDTUtils.findFolder(uri);
if (folder == null) {
continue;
}
IProject project = folder.getProject();
if (project != null) {
projectsToDelete.add(project);
}
}

WorkspaceJob job = new ImportProjectsFromSelectionJob(filesToImport, projectsToUpdate, projectsToDelete);
job.setRule(getWorkspaceRoot());
job.schedule();
}

@Override
public Job updateWorkspaceFolders(Collection<IPath> addedRootPaths, Collection<IPath> removedRootPaths) {
JavaLanguageServerPlugin.sendStatus(ServiceStatus.Message, "Updating workspace folders: Adding " + addedRootPaths.size() + " folder(s), removing " + removedRootPaths.size() + " folders.");
Expand Down Expand Up @@ -750,4 +793,87 @@ private void onDidConfigurationUpdated(MultiStatus status, IProgressMonitor moni
}
}
}

private final class ImportProjectsFromSelectionJob extends WorkspaceJob {
private final Set<IPath> filesToImport;
private final Set<IProject> projectsToUpdate;
private final Set<IProject> projectsToDelete;

private ImportProjectsFromSelectionJob(Set<IPath> filesToImport, Set<IProject> projectsToUpdate, Set<IProject> projectsToDelete) {
super("Applying the selected build files...");
this.filesToImport = filesToImport;
this.projectsToUpdate = projectsToUpdate;
this.projectsToDelete = projectsToDelete;
}

@Override
public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
try {
SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
deleteProjects(subMonitor);
importProjects(subMonitor);
updateProjects(projectsToUpdate, true);
return Status.OK_STATUS;
} catch (OperationCanceledException e) {
return Status.CANCEL_STATUS;
} catch (CoreException e) {
return new Status(IStatus.ERROR, IConstants.PLUGIN_ID, "Applying the selected build files failed.", e);
}
}

private void importProjects(SubMonitor subMonitor) throws CoreException {
importProjectsFromConfigurationFiles(
preferenceManager.getPreferences().getRootPaths(),
filesToImport,
subMonitor.split(70)
);
List<URI> projectUris = filesToImport.stream()
.map(path -> {
IFile file = JDTUtils.findFile(path.toFile().toURI().toString());
return file == null ? null : file.getProject();
})
.filter(Objects::nonNull)
.map(project -> ProjectUtils.getProjectRealFolder(project).toFile().toURI())
.collect(Collectors.toList());
if (!projectUris.isEmpty()) {
EventNotification notification = new EventNotification().withType(EventType.ProjectsImported).withData(projectUris);
client.sendEventNotification(notification);
}
}

private void deleteProjects(SubMonitor subMonitor) throws CoreException {
List<URI> projectUris = new LinkedList<>();
for (IProject project : projectsToDelete) {
clearDiagnostics(project);
// once the project is deleted, the project.getLocationURI() will return null, so we
// store the uri before deleting happens.
projectUris.add(ProjectUtils.getProjectRealFolder(project).toFile().toURI());
project.delete(false /*deleteContent*/, false /*force*/, subMonitor.split(1));
}
if (!projectUris.isEmpty()) {
EventNotification notification = new EventNotification().withType(EventType.ProjectsDeleted).withData(projectUris);
client.sendEventNotification(notification);
}
}

private void clearDiagnostics(IProject project) throws CoreException {
IMarker[] markers = project.findMarkers(null, true, IResource.DEPTH_INFINITE);
Set<String> uris = new HashSet<>();
for (IMarker marker : markers) {
URI locationURI = marker.getResource().getLocationURI();
if (locationURI != null) {
String uriString = locationURI.toString();
if (new File(locationURI).isDirectory() && Platform.OS_WIN32.equals(Platform.getOS()) &&
!uriString.endsWith("/")) {
uriString += "/";
}
uris.add(uriString);
}
}
for (String uri : uris) {
PublishDiagnosticsParams diagnostics = new PublishDiagnosticsParams(ResourceUtils.toClientUri(uri), Collections.emptyList());
client.publishDiagnostics(diagnostics);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,50 @@ public void testImportMavenSubModule() throws IOException, OperationCanceledExce
}
}

@Test
public void testChangeImportedMavenSubModule() throws Exception {
File projectDir = copyFiles("maven/multimodule", true);
Path projectDirPath = projectDir.toPath();
Collection<IPath> configurationPaths = new ArrayList<>();
Path subModuleConfiguration = projectDirPath.resolve("module1/pom.xml");
IPath filePath = ResourceUtils.canonicalFilePathFromURI(subModuleConfiguration.toUri().toString());
configurationPaths.add(filePath);
subModuleConfiguration = projectDirPath.resolve("module2/pom.xml");
filePath = ResourceUtils.canonicalFilePathFromURI(subModuleConfiguration.toUri().toString());
configurationPaths.add(filePath);
preferenceManager.getPreferences().setProjectConfigurations(configurationPaths);
projectsManager.initializeProjects(Collections.singleton(new org.eclipse.core.runtime.Path(projectDir.getAbsolutePath())), monitor);
IProject[] allProjects = ProjectUtils.getAllProjects();
Set<String> expectedProjects = new HashSet<>(Arrays.asList(
"module1",
"childmodule",
"module2",
"jdt.ls-java-project"
));
assertEquals(4, allProjects.length);
for (IProject project : allProjects) {
assertTrue(expectedProjects.contains(project.getName()));
}

Path newBuildFile = projectDirPath.resolve("module3/pom.xml");
List<String> toImport = Collections.singletonList(newBuildFile.toUri().toString());
IProject projectToRemove = WorkspaceHelper.getProject("module2");
List<String> toDelete = Collections.singletonList(projectToRemove.getLocationURI().toString());
projectsManager.changeImportedProjects(toImport, Collections.emptyList(), toDelete, monitor);
waitForBackgroundJobs();
allProjects = ProjectUtils.getAllProjects();
expectedProjects = new HashSet<>(Arrays.asList(
"module1",
"childmodule",
"module3",
"jdt.ls-java-project"
));
assertEquals(4, allProjects.length);
for (IProject project : allProjects) {
assertTrue(expectedProjects.contains(project.getName()));
}
}

@Test
public void testImportMixedProjects() throws IOException, OperationCanceledException, CoreException {
File projectDir = copyFiles("mixed", true);
Expand Down

0 comments on commit 57847f4

Please sign in to comment.