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 22, 2023
1 parent 58fc1d6 commit 49fbeb9
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,12 @@ public Object executeCommand(String commandId, List<Object> arguments, IProgress
}
return DiagnosticsCommand.refreshDiagnostics((String) arguments.get(0), (String) arguments.get(1), (boolean) arguments.get(2), (boolean) arguments.get(3));
case "java.project.import":
ProjectCommand.importProject(monitor);
if (arguments.size() > 0) {
List<String> buildFileUris = (ArrayList<String>) arguments.get(0);
ProjectCommand.importProject(monitor, buildFileUris.toArray(new String[0]));
} else {
ProjectCommand.importProject(monitor);
}
return null;
case "java.project.resolveStackTraceLocation":
List<String> projectNames = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,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 @@ -224,8 +221,8 @@ public static List<URI> getAllJavaProjects() {
return javaProjects;
}

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

private static IPath[] listTestSourcePaths(IJavaProject project) throws JavaModelException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
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.List;
import java.util.Map;
import java.util.Map.Entry;
Expand All @@ -35,6 +37,7 @@
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.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 +76,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 +87,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 @@ -207,7 +212,65 @@ private Set<IPath> removeImportedConfigurations(Set<IPath> configurations, IProj
.collect(Collectors.toSet());
}

public void importProjects(IProgressMonitor monitor) {
public void importProjects(IProgressMonitor monitor, String... buildFileUris) {
if (buildFileUris == null || buildFileUris.length == 0) {
importProjectsAutomatically();
return;
}
importProjectsFromSelection(Arrays.asList(buildFileUris));
}

private void importProjectsFromSelection(Collection<String> projectConfigurations) {
Set<IPath> filesToImport = new HashSet<>();
Set<IProject> projectsToUpdate = new HashSet<>();
Set<IPath> projectLocations = new HashSet<>();
for (String uri : projectConfigurations) {
IPath uriPath = ResourceUtils.canonicalFilePathFromURI(uri);
IProject project = findBelongingProject(uri);
if (project != null) {
projectsToUpdate.add(project);
} else {
filesToImport.add(uriPath);
}
projectLocations.add(uriPath.removeLastSegments(1));
}

Set<IProject> projectsToDelete = new HashSet<>();
Collection<String> clientSupportBuildFiles = preferenceManager.getClientPreferences().getClientSupportBuildFiles();
for (IProject project : ProjectUtils.getAllProjects()) {
if (ProjectsManager.DEFAULT_PROJECT_NAME.equals(project.getName())) {
continue;
}
if (projectLocations.contains(project.getLocation())) {
continue;
}
Optional<IBuildSupport> bsOptional = getBuildSupport(project);
if (bsOptional.isEmpty()) {
continue;
}
IBuildSupport buildSupport = bsOptional.get();
// if the build file of this imported project is also supported by the client, but not
// exists in the containing directories of selected build files, we need to delete it.
boolean isClientSupported = clientSupportBuildFiles.stream().anyMatch(buildSupport::isBuildLikeFileName);
if (isClientSupported) {
projectsToDelete.add(project);
}
}

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

private IProject findBelongingProject(String uri) {
IFile file = JDTUtils.findFile(uri);
if (file == null) {
return null;
}
return file.getProject();
}

private void importProjectsAutomatically() {
WorkspaceJob job = new WorkspaceJob("Importing projects in workspace...") {

@Override
Expand Down Expand Up @@ -750,4 +813,63 @@ 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);
importProjectsFromConfigurationFiles(
preferenceManager.getPreferences().getRootPaths(),
filesToImport,
subMonitor.split(70)
);
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 deleteProjects(SubMonitor subMonitor) throws CoreException {
for (IProject project : projectsToDelete) {
clearDiagnostics(project);
project.delete(false /*deleteContent*/, false /*force*/, subMonitor.split(1));
}
}

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 @@ -489,4 +489,11 @@ public boolean skipProjectConfiguration() {
public boolean skipTextEventPropagation() {
return Boolean.parseBoolean(extendedClientCapabilities.getOrDefault("skipTextEventPropagation", "false").toString());
}

public Collection<String> getClientSupportBuildFiles() {
Object list = extendedClientCapabilities.getOrDefault("supportBuildFiles", null);
return list instanceof Collection<?> supportBuildFiles
? supportBuildFiles.stream().filter(String.class::isInstance).map(String.class::cast).toList()
: List.of();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URI;
Expand All @@ -50,7 +49,6 @@
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRunnable;
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.IRegistryChangeEvent;
Expand Down Expand Up @@ -110,21 +108,17 @@ public abstract class AbstractProjectsManagerBasedTest {

protected Map<String, List<Object>> clientRequests = new HashMap<>();

protected JavaLanguageClient client = (JavaLanguageClient) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { JavaLanguageClient.class }, new InvocationHandler() {

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (args.length == 1) {
String name = method.getName();
List<Object> params = clientRequests.get(name);
if (params == null) {
params = new ArrayList<>();
clientRequests.put(name, params);
}
params.add(args[0]);
protected JavaLanguageClient client = (JavaLanguageClient) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { JavaLanguageClient.class }, (InvocationHandler) (proxy, method, args) -> {
if (args.length == 1) {
String name = method.getName();
List<Object> params = clientRequests.get(name);
if (params == null) {
params = new ArrayList<>();
clientRequests.put(name, params);
}
return null;
params.add(args[0]);
}
return null;
});

@Before
Expand Down Expand Up @@ -186,6 +180,7 @@ protected ClientPreferences initPreferenceManager(boolean supportClassFileConten
Mockito.lenient().when(clientPreferences.isAdvancedGenerateAccessorsSupported()).thenReturn(true);
Mockito.lenient().when(clientPreferences.isGenerateConstructorsPromptSupported()).thenReturn(true);
Mockito.lenient().when(clientPreferences.isGenerateDelegateMethodsPromptSupported()).thenReturn(true);
Mockito.lenient().when(clientPreferences.getClientSupportBuildFiles()).thenReturn(Arrays.asList("pom.xml"));
return clientPreferences;
}

Expand Down Expand Up @@ -237,12 +232,7 @@ protected List<IProject> importProjects(Collection<String> paths, boolean delete
roots.add(Path.fromOSString(file.getAbsolutePath()));
}
waitForBackgroundJobs();
IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
projectsManager.initializeProjects(roots, monitor);
}
};
IWorkspaceRunnable runnable = monitor -> projectsManager.initializeProjects(roots, monitor);
JavaCore.run(runnable, null, monitor);
waitForBackgroundJobs();
return WorkspaceHelper.getAllProjects();
Expand Down Expand Up @@ -468,12 +458,7 @@ protected IProject importRootFolder(IPath rootPath, String triggerFile) throws E
preferences.setTriggerFiles(Arrays.asList(triggerFilePath));
}
final List<IPath> roots = Arrays.asList(rootPath);
IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
projectsManager.initializeProjects(roots, monitor);
}
};
IWorkspaceRunnable runnable = monitor -> projectsManager.initializeProjects(roots, monitor);
JavaCore.run(runnable, null, monitor);
waitForBackgroundJobs();
String invisibleProjectName = ProjectUtils.getWorkspaceInvisibleProjectName(rootPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,45 @@ 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");
projectsManager.importProjects(new NullProgressMonitor(), newBuildFile.toUri().toString());
waitForBackgroundJobs();
allProjects = ProjectUtils.getAllProjects();
expectedProjects = new HashSet<>(Arrays.asList(
"module3",
"jdt.ls-java-project"
));
assertEquals(2, 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 49fbeb9

Please sign in to comment.