Skip to content

Commit

Permalink
New folders do not register in the jdt.ls workspace eclipse-che#10115
Browse files Browse the repository at this point in the history
Draft for reporting creation/modification/deletion of source folders to jdt.ls

Signed-off-by: Victor Rubezhny <[email protected]>
  • Loading branch information
vrubezhny committed Aug 22, 2018
1 parent b92c471 commit 3c3ddc6
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
Expand All @@ -38,6 +42,14 @@
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
Expand Down Expand Up @@ -86,6 +98,10 @@
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-java-server</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.lsp4j</groupId>
<artifactId>org.eclipse.lsp4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ protected void configure() {
newSetBinder(binder(), ProjectHandler.class).addBinding().to(PlainJavaInitHandler.class);

bind(ClasspathUpdaterService.class);
bind(PlainJavaProjectSourceFolderWatcher.class).asEagerSingleton();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.plugin.java.plain.server.inject;

import static java.nio.file.Files.isDirectory;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
import static org.eclipse.che.api.languageserver.LanguageServiceUtils.prefixURI;
import static org.eclipse.che.api.languageserver.LanguageServiceUtils.removePrefixUri;
import static org.eclipse.che.api.languageserver.LanguageServiceUtils.removeUriScheme;
import static org.eclipse.che.plugin.java.plain.shared.PlainJavaProjectConstants.DEFAULT_SOURCE_FOLDER_VALUE;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.languageserver.LanguageServerInitializedEvent;
import org.eclipse.che.api.project.server.ProjectManager;
import org.eclipse.che.api.project.server.notification.ProjectUpdatedEvent;
import org.eclipse.che.api.project.shared.RegisteredProject;
import org.eclipse.che.api.watcher.server.FileWatcherManager;
import org.eclipse.che.api.watcher.server.impl.FileWatcherByPathMatcher;
import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler;
import org.eclipse.che.plugin.java.languageserver.JavaLanguageServerExtensionService;
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
import org.eclipse.lsp4j.FileChangeType;
import org.eclipse.lsp4j.FileEvent;
import org.eclipse.lsp4j.services.LanguageServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Reports the create/update/delete changes on project source folders to jdt.ls
*
* @author V. Rubezhny
*/
public class PlainJavaProjectSourceFolderWatcher {
private static final Logger LOG =
LoggerFactory.getLogger(PlainJavaProjectSourceFolderWatcher.class);

private static final List<String> EMPTY_SOURCE_FOLDERS = emptyList();

private final FileWatcherManager manager;
private final FileWatcherByPathMatcher matcher;
private final EventService eventService;
private final ExecutorService executorService;

private final CopyOnWriteArrayList<Integer> watcherIds = new CopyOnWriteArrayList<>();
private final JavaLanguageServerExtensionService extensionService;
private final CopyOnWriteArrayList<LanguageServer> languageServers = new CopyOnWriteArrayList<>();

private final ProjectManager projectManager;

@Inject
public PlainJavaProjectSourceFolderWatcher(
FileWatcherManager manager,
FileWatcherByPathMatcher matcher,
EventService eventService,
JavaLanguageServerExtensionService extensionService,
ProjectManager projectManager) {
this.manager = manager;
this.matcher = matcher;
this.eventService = eventService;
this.extensionService = extensionService;
this.projectManager = projectManager;
this.executorService =
Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder()
.setNameFormat("WorkspaceUpdater-%d")
.setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance())
.setDaemon(true)
.build());
}

@PostConstruct
protected void startWatchers() {
int watcherId =
manager.registerByMatcher(
folderMatcher(),
s -> report(s, FileChangeType.Created),
s -> report(s, FileChangeType.Changed),
s -> report(s, FileChangeType.Deleted));

watcherIds.add(watcherId);
eventService.subscribe(this::onServerInitialized, LanguageServerInitializedEvent.class);
eventService.subscribe(this::onProjectUpdated, ProjectUpdatedEvent.class);
}

@PreDestroy
public void stopWatchers() {
watcherIds.stream().forEach(id -> manager.unRegisterByMatcher(id));
}

private void onServerInitialized(LanguageServerInitializedEvent event) {
LOG.debug(
"Registering source folder watching operations for language server : {}", event.getId());

String id = event.getId();
LanguageServer languageServer = event.getLanguageServer();
languageServers.add(languageServer);
}

private void onProjectUpdated(ProjectUpdatedEvent event) {
executorService.submit(
() -> {
setInitialPathsToWatch(prefixURI(event.getProjectPath()));
});
}

private PathMatcher folderMatcher() {
return it -> isSourceFolder(it);
}

private void report(String path, FileChangeType changeType) {
languageServers.stream().forEach(ls -> send(ls, path, changeType));
}

private void send(LanguageServer server, String path, FileChangeType changeType) {
RegisteredProject project = projectManager.getClosestOrNull(path);
if (project == null) {
return;
}
DidChangeWatchedFilesParams params =
new DidChangeWatchedFilesParams(
Collections.singletonList(new FileEvent(prefixURI(path), changeType)));
LOG.info("[send] Server {}, Path {}, ChangeType: {}", server, path, changeType);
server.getWorkspaceService().didChangeWatchedFiles(params);
}

private boolean isSourceFolder(Path path) {
if (!isDirectory(path)) {
LOG.info("[isSourceFolder] Path {}: [!isDirectory]: FALSE", path);
return false;
}

String wsPath = removePrefixUri(path.toUri().toString());
RegisteredProject project = projectManager.getClosestOrNull(wsPath);
if (project == null) {
LOG.info("[isSourceFolder] Path {}: [getClosestOrNull(wsPath) == null]: FALSE", path);
return false;
}

boolean res =
(getSourceFolders(project.getPath())
.stream()
.filter(p -> Paths.get(wsPath).startsWith(Paths.get(p)))
.count()
> 0);

LOG.info("[isSourceFolder] Path {}: result: {}", path, (res ? "TRUE" : "FALSE"));

return res;
}

private void setInitialPathsToWatch(String projectUri) {
// LOG.info("[setInitialPathsToWatch] project {}", projectUri);
List<String> sourceFolderLocations;
try {
sourceFolderLocations = extensionService.getAllSourceFoldersLocations(projectUri);
} catch (Exception e) {
return;
}
if (sourceFolderLocations != null) {
sourceFolderLocations
.stream()
.forEach(f -> matcher.accept(Paths.get(removeUriScheme(prefixURI(f)))));
}
}

private List<String> getSourceFolders(String path) {
List<String> sourceFolders;
try {
sourceFolders = extensionService.getSourceFolders(path);
} catch (Exception e) {
return EMPTY_SOURCE_FOLDERS;
}

List<String> filteredResult =
sourceFolders
.stream()
.filter(p -> Paths.get(p).startsWith(Paths.get(path)))
.collect(toList());

return filteredResult.isEmpty() ? singletonList(DEFAULT_SOURCE_FOLDER_VALUE) : filteredResult;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import static org.eclipse.che.jdt.ls.extension.api.Commands.GET_LIBRARY_CHILDREN_COMMAND;
import static org.eclipse.che.jdt.ls.extension.api.Commands.GET_LIBRARY_ENTRY_COMMAND;
import static org.eclipse.che.jdt.ls.extension.api.Commands.GET_OUTPUT_DIR_COMMAND;
import static org.eclipse.che.jdt.ls.extension.api.Commands.GET_PROJECT_SOURCE_LOCATIONS_COMMAND;
import static org.eclipse.che.jdt.ls.extension.api.Commands.GET_SOURCE_FOLDERS;
import static org.eclipse.che.jdt.ls.extension.api.Commands.REIMPORT_MAVEN_PROJECTS_COMMAND;
import static org.eclipse.che.jdt.ls.extension.api.Commands.RENAME_COMMAND;
Expand Down Expand Up @@ -456,6 +457,20 @@ public List<String> getSourceFolders(String projectPath) {
return result.stream().map(LanguageServiceUtils::removePrefixUri).collect(Collectors.toList());
}

/**
* Gets all source folder locations of plain java project.
*
* @param projectPath project path
* @return source folder locations
*/
public List<String> getAllSourceFoldersLocations(String projectPath) {
checkLanguageServerInitialized();

String projectUri = LanguageServiceUtils.prefixURI(projectPath);
Type type = new TypeToken<ArrayList<String>>() {}.getType();
return doGetList(GET_PROJECT_SOURCE_LOCATIONS_COMMAND, projectUri, type);
}

private void checkLanguageServerInitialized() {
if (findInitializedLanguageServer() == null) {
throw new IllegalStateException("Language server isn't initialized");
Expand Down Expand Up @@ -987,8 +1002,11 @@ private <T, P> List<T> doGetList(String command, P params, Type type) {
private <T> List<T> doGetList(String command, List<Object> params, Type type) {
CompletableFuture<Object> result = executeCommand(command, params);
try {
return gson.fromJson(gson.toJson(result.get(TIMEOUT, TimeUnit.SECONDS)), type);
Object res = result.get(60, TimeUnit.SECONDS);
LOG.info("[doGetList] got the result object, converting to json");
return gson.fromJson(gson.toJson(res), type);
} catch (JsonSyntaxException | InterruptedException | ExecutionException | TimeoutException e) {
LOG.error("[doGetList] Error getting the result object: " + e.getClass().getName(), e);
throw new JsonRpcException(-27000, e.getMessage());
}
}
Expand All @@ -1015,8 +1033,12 @@ private CompletableFuture<Object> executeCommand(String commandId, List<Object>
(ServerCapabilities cap) -> {
ExtendedLanguageServer ls = findInitializedLanguageServer();
if (ls != null) {
return ls.getWorkspaceService().executeCommand(params);
LOG.info("[executeCommand] about to execute command: " + commandId);
CompletableFuture<Object> result = ls.getWorkspaceService().executeCommand(params);
LOG.info("[executeCommand] command executed: " + commandId);
return result;
} else {
LOG.info("[executeCommand] language server not found for command: " + commandId);
CompletableFuture<Object> completedFuture = CompletableFuture.completedFuture(null);
completedFuture.completeExceptionally(
new LanguageServerException("did not find language server"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.eclipse.che.ide.api.project.type.wizard.PreSelectedProjectTypeManager;
import org.eclipse.che.ide.ext.java.client.action.GetEffectivePomAction;
import org.eclipse.che.ide.ext.java.client.action.ReimportMavenDependenciesAction;
import org.eclipse.che.plugin.maven.client.comunnication.MavenMessagesHandler;
import org.eclipse.che.plugin.maven.client.editor.ClassFileSourcesDownloader;
import org.eclipse.che.plugin.maven.client.project.MavenModelImporter;
import org.eclipse.che.plugin.maven.client.project.ResolvingMavenProjectStateHolder;
Expand All @@ -49,7 +50,7 @@ public class MavenExtension {
@Inject
public MavenExtension(
PreSelectedProjectTypeManager preSelectedProjectManager,
// MavenMessagesHandler messagesHandler,
MavenMessagesHandler messagesHandler,
ClassFileSourcesDownloader downloader,
MavenModelImporter importMavenModelHandler,
MavenResources resources,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,22 @@
import static org.eclipse.che.selenium.core.constant.TestProjectExplorerContextMenuConstants.SubMenuBuildPath.USE_AS_SOURCE_FOLDER;
import static org.eclipse.che.selenium.pageobject.ProjectExplorer.FolderTypes.JAVA_SOURCE_FOLDER;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.Inject;
import java.net.URL;
import java.nio.file.Paths;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler;
import org.eclipse.che.selenium.core.client.TestProjectServiceClient;
import org.eclipse.che.selenium.core.constant.TestMenuCommandsConstants;
import org.eclipse.che.selenium.core.constant.TestProjectExplorerContextMenuConstants;
import org.eclipse.che.selenium.core.project.ProjectTemplates;
import org.eclipse.che.selenium.core.utils.WaitUtils;
import org.eclipse.che.selenium.core.workspace.TestWorkspace;
import org.eclipse.che.selenium.pageobject.AskForValueDialog;
import org.eclipse.che.selenium.pageobject.Consoles;
import org.eclipse.che.selenium.pageobject.Ide;
import org.eclipse.che.selenium.pageobject.Loader;
import org.eclipse.che.selenium.pageobject.Menu;
Expand Down Expand Up @@ -57,13 +63,24 @@ public class MoveJavaFileInNewSourceFolderTest {
@Inject private ToastLoader toastLoader;
@Inject private NotificationsPopupPanel notificationsPopupPanel;
@Inject private TestProjectServiceClient testProjectServiceClient;
@Inject private Consoles consoles;

private ExecutorService executorService;

@BeforeClass
public void prepare() throws Exception {
URL resource = getClass().getResource("/projects/plain-java-project");
testProjectServiceClient.importProject(
workspace.getId(), Paths.get(resource.toURI()), PROJECT_NAME, ProjectTemplates.PLAIN_JAVA);
ide.open(workspace);
consoles.waitJDTLSProjectResolveFinishedMessage(PROJECT_NAME);
this.executorService =
Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder()
.setNameFormat("WorkspaceUpdater-%d")
.setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance())
.setDaemon(true)
.build());
}

@Test
Expand All @@ -81,7 +98,11 @@ public void checkMoveJavaClassInNewSourceFolder() {
projectExplorer.waitAndSelectItem(PATH_NEW_SOURCE_FOLDER);
projectExplorer.openContextMenuByPathSelectedItem(PROJECT_NAME + "/test/" + NEW_SOURCE_FOLDER);
projectExplorer.clickOnItemInContextMenu(BUILD_PATH);
projectExplorer.clickOnItemInContextMenu(USE_AS_SOURCE_FOLDER);
// executorService.submit(
// () -> {
projectExplorer.clickOnItemInContextMenu(USE_AS_SOURCE_FOLDER);
// });
WaitUtils.sleepQuietly(5);
projectExplorer.waitDefinedTypeOfFolder(PATH_NEW_SOURCE_FOLDER, JAVA_SOURCE_FOLDER);
projectExplorer.waitAndSelectItem(PATH_NEW_SOURCE_FOLDER);
projectExplorer.openContextMenuByPathSelectedItem(PATH_NEW_SOURCE_FOLDER);
Expand All @@ -90,6 +111,10 @@ public void checkMoveJavaClassInNewSourceFolder() {
// move java file into new source folder
projectExplorer.waitItem(PROJECT_NAME);
projectExplorer.waitAndSelectItem(PATH_TO_FILE);

// As FileTreeWalker's period is 10 secs - wait for the folders to be finally reported to jdt.ls
WaitUtils.sleepQuietly(11);

projectExplorer.launchRefactorMoveByKeyboard();
refactor.waitMoveItemFormIsOpen();
refactor.clickOnExpandIconTree(PROJECT_NAME);
Expand Down

0 comments on commit 3c3ddc6

Please sign in to comment.