Ensure fallbacks kick in JDT LS

When either JDT LS is too old, or
not available... then disbabled the JdtLsProjectCache so that maven / gradle will be used instead.
This commit is contained in:
Kris De Volder
2018-03-26 14:51:36 -07:00
parent 9bc0c20937
commit 13633746cb
7 changed files with 159 additions and 65 deletions

View File

@@ -660,7 +660,7 @@ public class SimpleLanguageServer implements Sts4LanguageServer, LanguageClientA
return this.async;
}
public synchronized Disposable addClasspathListener(ClasspathListener classpathListener) {
public synchronized Disposable addClasspathListener(ClasspathListener classpathListener) throws Exception {
if (classpathListenerManager == null) {
classpathListenerManager = new ClasspathListenerManager(this);
}

View File

@@ -16,7 +16,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.internal.runtime.Log;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
@@ -29,6 +28,17 @@ import org.springframework.tooling.jdt.ls.extension.ClasspathListenerManager.Cla
@SuppressWarnings("restriction")
public class ClasspathListenerHandler implements IDelegateCommandHandler {
static final boolean isSupported = checkSupported();
private static boolean checkSupported() {
try {
JavaClientConnection.class.getMethod("executeClientCommand", String.class, Object[].class);
return true;
} catch (Exception e) {
Logger.log(e);
}
return false;
}
static class Subscribptions {
@@ -101,6 +111,9 @@ public class ClasspathListenerHandler implements IDelegateCommandHandler {
@Override
public Object executeCommand(String commandId, List<Object> arguments, IProgressMonitor monitor) throws Exception {
if (!isSupported) {
throw new UnsupportedOperationException("Command '"+commandId+"' not supported on older versions of JDT Language Server");
}
log("ClasspathListenerHandler executeCommand " + commandId + ", " + arguments);
switch (commandId) {
case "sts.java.addClasspathListener":

View File

@@ -13,11 +13,13 @@ package org.springframework.ide.vscode.boot;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.Arrays;
import java.util.Optional;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
import org.springframework.ide.vscode.boot.java.utils.SpringLiveHoverWatchdog;
import org.springframework.ide.vscode.boot.jdt.ls.JdtLsProjectCache;
import org.springframework.ide.vscode.boot.jdt.ls.JavaProjectsService;
import org.springframework.ide.vscode.boot.metadata.DefaultSpringPropertyIndexProvider;
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
@@ -31,6 +33,7 @@ import org.springframework.ide.vscode.commons.languageserver.java.CompositeJavaP
import org.springframework.ide.vscode.commons.languageserver.java.CompositeProjectOvserver;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver.Listener;
import org.springframework.ide.vscode.commons.languageserver.util.LSFactory;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.maven.MavenCore;
@@ -80,32 +83,51 @@ public class BootLanguageServerParams {
public static LSFactory<BootLanguageServerParams> createDefault() {
return (SimpleLanguageServer server) -> {
// Initialize project finders, project caches and project observers
CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder();
JdtLsProjectCache jdtProjectCache = new JdtLsProjectCache(server);
javaProjectFinder.addJavaProjectFinder(jdtProjectCache);
MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER));
javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache));
GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER));
javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache));
CompositeProjectOvserver projectObserver = new CompositeProjectOvserver(Arrays.asList(jdtProjectCache, mavenProjectCache, gradleProjectCache));
DefaultSpringPropertyIndexProvider indexProvider = new DefaultSpringPropertyIndexProvider(javaProjectFinder, projectObserver);
JdtLsProjectCache jdtProjectCache = new JdtLsProjectCache(server, createFallbackProjectCache(server));
DefaultSpringPropertyIndexProvider indexProvider = new DefaultSpringPropertyIndexProvider(jdtProjectCache, jdtProjectCache);
indexProvider.setProgressService(server.getProgressService());
return new BootLanguageServerParams(
javaProjectFinder.filter(BootProjectUtil::isBootProject),
projectObserver,
jdtProjectCache.filter(BootProjectUtil::isBootProject),
jdtProjectCache,
indexProvider,
(IDocument doc) -> new TypeUtil(javaProjectFinder.find(new TextDocumentIdentifier(doc.getUri()))),
(IDocument doc) -> new TypeUtil(jdtProjectCache.find(new TextDocumentIdentifier(doc.getUri()))),
RunningAppProvider.DEFAULT,
SpringLiveHoverWatchdog.DEFAULT_INTERVAL
);
};
}
private static JavaProjectsService createFallbackProjectCache(SimpleLanguageServer server) {
CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder();
MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER));
javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache));
GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER));
javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache));
CompositeProjectOvserver projectObserver = new CompositeProjectOvserver(Arrays.asList(mavenProjectCache, gradleProjectCache));
return new JavaProjectsService() {
@Override
public void removeListener(Listener listener) {
projectObserver.removeListener(listener);
}
@Override
public void addListener(Listener listener) {
projectObserver.addListener(listener);
}
@Override
public Optional<IJavaProject> find(TextDocumentIdentifier doc) {
return javaProjectFinder.find(doc);
}
};
}
public static LSFactory<BootLanguageServerParams> createTestDefault(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider) {
return (SimpleLanguageServer server) -> {
// Initialize project finders, project caches and project observers

View File

@@ -0,0 +1,17 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.jdt.ls;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
public interface JavaProjectsService extends JavaProjectFinder, ProjectObserver {
}

View File

@@ -30,66 +30,105 @@ import org.springframework.ide.vscode.commons.java.ClasspathData;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IJavadocProvider;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.ClasspathListener;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.CollectorUtil;
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
import org.springframework.ide.vscode.commons.util.UriUtil;
import com.google.common.collect.ImmutableList;
import reactor.core.Disposable;
public class JdtLsProjectCache implements JavaProjectFinder, ProjectObserver {
public class JdtLsProjectCache implements JavaProjectsService {
private CompletableFuture<Void> initialized = new CompletableFuture<Void>();
private SimpleLanguageServer server;
private Map<String, JdtLsProject> table = new HashMap<String, JdtLsProject>();
private Logger log = LoggerFactory.getLogger(JdtLsProjectCache.class);
private List<Listener> listeners = new ArrayList<>();
public JdtLsProjectCache(SimpleLanguageServer server) {
private final JavaProjectsService fallback;
public JdtLsProjectCache(SimpleLanguageServer server, JavaProjectsService fallback) {
Assert.isNotNull(fallback);
this.fallback = fallback;
this.server = server;
CompletableFuture<Disposable> disposable = new CompletableFuture<Disposable>();
this.server.onInitialized(() ->
disposable.complete(server.addClasspathListener(new ClasspathListener() {
@Override
public void changed(Event event) {
synchronized (table) {
String uri = UriUtil.normalize(event.projectUri);
if (event.deleted) {
JdtLsProject deleted = table.remove(uri);
notifyDelete(deleted);
} else {
JdtLsProject newProject = new JdtLsProject(event.name, uri, event.classpath);
JdtLsProject oldProject = table.put(uri, newProject);
if (oldProject != null) {
notifyChanged(newProject);
this.server.onInitialized(() -> {
try {
disposable.complete(server.addClasspathListener(new ClasspathListener() {
@Override
public void changed(Event event) {
synchronized (table) {
String uri = UriUtil.normalize(event.projectUri);
if (event.deleted) {
JdtLsProject deleted = table.remove(uri);
notifyDelete(deleted);
} else {
notifyCreated(newProject);
JdtLsProject newProject = new JdtLsProject(event.name, uri, event.classpath);
JdtLsProject oldProject = table.put(uri, newProject);
if (oldProject != null) {
notifyChanged(newProject);
} else {
notifyCreated(newProject);
}
}
}
}
}));
initialized.complete(null);
} catch (Throwable e) {
if (isNoJdtError(e)) {
log.info("JDT Language Server not available. Fallback classpath provider will be used instead.");
} else if (isOldJdt(e)) {
log.info("JDT Lanuage Server too old. Fallback classpath provider will be used instead.");
} else {
log.error("Unexpected error registering classpath listener with JDT. Fallback classpath provider will be used instead.", e);
}
}))
);
disposable.complete(()-> {});
initialized.completeExceptionally(e);
}
});
this.server.onShutdown(() ->
disposable.thenAccept(Disposable::dispose).join()
);
}
private boolean isOldJdt(Throwable e) {
return ExceptionUtil.getMessage(e).contains("'sts.java.addClasspathListener' not supported");
}
private boolean isNoJdtError(Throwable e) {
return ExceptionUtil.getMessage(e).contains("command 'java.execute.workspaceCommand' not found");
}
@Override
public void addListener(Listener listener) {
synchronized (listeners) {
listeners.add(listener);
Assert.isLegal(initialized.isDone()); //Adding / removing listeners prior to inialization isn't supported. (If this is really needed, it can be
// bit its a bit trickier to implement that correctly)
if (initialized.isCompletedExceptionally()) {
fallback.addListener(listener);
} else {
synchronized (listeners) {
listeners.add(listener);
}
}
}
@Override
public void removeListener(Listener listener) {
synchronized (listeners) {
listeners.remove(listener);
Assert.isLegal(initialized.isDone()); //Adding / removing listeners prior to inialization isn't supported. (If this is really needed, it can be
// bit its a bit trickier to implement that correctly)
if (initialized.isCompletedExceptionally()) {
fallback.removeListener(listener);
} else {
synchronized (listeners) {
listeners.remove(listener);
}
}
}
@@ -130,17 +169,21 @@ public class JdtLsProjectCache implements JavaProjectFinder, ProjectObserver {
@Override
public Optional<IJavaProject> find(TextDocumentIdentifier doc) {
String uri = UriUtil.normalize(doc.getUri());
synchronized (table) {
for (Entry<String, JdtLsProject> e : table.entrySet()) {
String projectUri = e.getKey();
if (UriUtil.contains(projectUri, uri) ) {
return Optional.of(e.getValue());
}
if (initialized.isDone()) {
if (initialized.isCompletedExceptionally()) {
return fallback.find(doc);
}
String uri = UriUtil.normalize(doc.getUri());
synchronized (table) {
for (Entry<String, JdtLsProject> e : table.entrySet()) {
String projectUri = e.getKey();
if (UriUtil.contains(projectUri, uri) ) {
return Optional.of(e.getValue());
}
}
}
}
return Optional.empty();
}

View File

@@ -8,17 +8,18 @@ cd ${HOME}/git/eclipse.jdt.ls
jdt_ls_repo=$(pwd)
./mvnw -Pserver-distro -Pupdate-site clean package
# Section below disabled for now. So we can work on the 'fallbacks' with missing or old vscode-java
# Build vscode-java
cd ${HOME}/git/vscode-java
rm -fr server
mkdir server
tar -xvzf ${jdt_ls_repo}/org.eclipse.jdt.ls.product/distro/jdt-language-server-*.tar.gz -C ./server
npm install
rm -fr *.vsix
npm install -D vsce
./node_modules/.bin/vsce package
code --uninstall-extension redhat.java || echo "Not installed redhat.java"
code --install-extension *.vsix
#cd ${HOME}/git/vscode-java
#rm -fr server
#mkdir server
#tar -xvzf ${jdt_ls_repo}/org.eclipse.jdt.ls.product/distro/jdt-language-server-*.tar.gz -C ./server
#npm install
#rm -fr *.vsix
#npm install -D vsce
#./node_modules/.bin/vsce package
#code --uninstall-extension redhat.java || echo "Not installed redhat.java"
#code --install-extension *.vsix
# Build spring boot ls
cd $workdir

View File

@@ -1,6 +1,4 @@
'use strict';
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as VSCode from 'vscode';
import * as Path from 'path';