Refactor javadoc service injection

This commit is contained in:
BoykoAlex
2018-06-05 09:53:52 -04:00
parent 9e612bb10e
commit 4b9e29590b
23 changed files with 362 additions and 250 deletions

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* Copyright (c) 2017, 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
@@ -18,8 +18,9 @@ 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.jdt.ls.JavaProjectsServiceWithFallback;
import org.springframework.ide.vscode.boot.jdt.ls.JdtLsProjectCache;
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;
@@ -29,11 +30,14 @@ import org.springframework.ide.vscode.commons.gradle.GradleProjectCache;
import org.springframework.ide.vscode.commons.gradle.GradleProjectFinder;
import org.springframework.ide.vscode.commons.java.BootProjectUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IJavadocProvider;
import org.springframework.ide.vscode.commons.javadoc.JavaDocProviders;
import org.springframework.ide.vscode.commons.languageserver.java.CompositeJavaProjectFinder;
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.JavadocService;
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.jdt.ls.Classpath.CPE;
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;
@@ -83,7 +87,11 @@ public class BootLanguageServerParams {
public static LSFactory<BootLanguageServerParams> createDefault() {
return (SimpleLanguageServer server) -> {
// Initialize project finders, project caches and project observers
JdtLsProjectCache jdtProjectCache = new JdtLsProjectCache(server, () -> createFallbackProjectCache(server));
JavaProjectsService jdtProjectCache = new JavaProjectsServiceWithFallback(
server,
new JdtLsProjectCache(server),
() -> createFallbackProjectCache(server)
);
DefaultSpringPropertyIndexProvider indexProvider = new DefaultSpringPropertyIndexProvider(jdtProjectCache, jdtProjectCache);
indexProvider.setProgressService(server.getProgressService());
@@ -101,10 +109,12 @@ public class BootLanguageServerParams {
private static JavaProjectsService createFallbackProjectCache(SimpleLanguageServer server) {
CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder();
MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER));
JavadocService javadocService = (uri, cpe) -> JavaDocProviders.createFor(cpe);
MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER), javadocService);
javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache));
GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER));
GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER), javadocService);
javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache));
CompositeProjectOvserver projectObserver = new CompositeProjectOvserver(Arrays.asList(mavenProjectCache, gradleProjectCache));
@@ -125,6 +135,11 @@ public class BootLanguageServerParams {
public Optional<IJavaProject> find(TextDocumentIdentifier doc) {
return javaProjectFinder.find(doc);
}
@Override
public IJavadocProvider javadocProvider(String projectUri, CPE cpe) {
return javadocService.javadocProvider(projectUri, cpe);
}
};
}
@@ -132,11 +147,11 @@ public class BootLanguageServerParams {
return (SimpleLanguageServer server) -> {
// Initialize project finders, project caches and project observers
CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder();
MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), false, null);
MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), false, null, (uri, cpe) -> JavaDocProviders.createFor(cpe));
mavenProjectCache.setAlwaysFireEventOnFileChanged(true);
javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache));
GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), false, null);
GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), false, null, (uri, cpe) -> JavaDocProviders.createFor(cpe));
gradleProjectCache.setAlwaysFireEventOnFileChanged(true);
javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache));
@@ -157,11 +172,11 @@ public class BootLanguageServerParams {
return (SimpleLanguageServer server) -> {
// Initialize project finders, project caches and project observers
CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder();
MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), false, null);
MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), false, null, (uri, cpe) -> JavaDocProviders.createFor(cpe));
mavenProjectCache.setAlwaysFireEventOnFileChanged(true);
javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache));
GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), false, null);
GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), false, null, (uri, cpe) -> JavaDocProviders.createFor(cpe));
gradleProjectCache.setAlwaysFireEventOnFileChanged(true);
javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache));

View File

@@ -0,0 +1,19 @@
/*******************************************************************************
* 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 reactor.core.Disposable;
public interface InitializableJavaProjectsService extends JavaProjectsService {
Disposable initialize() throws Exception;
}

View File

@@ -11,7 +11,8 @@
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.JavadocService;
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
public interface JavaProjectsService extends JavaProjectFinder, ProjectObserver {
public interface JavaProjectsService extends JavaProjectFinder, ProjectObserver, JavadocService {
}

View File

@@ -0,0 +1,114 @@
/*******************************************************************************
* 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 java.util.Optional;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IJavadocProvider;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath.CPE;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.util.Assert;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import reactor.core.Disposable;
public class JavaProjectsServiceWithFallback implements JavaProjectsService {
private Logger log = LoggerFactory.getLogger(JavaProjectsServiceWithFallback.class);
final private CompletableFuture<Void> initialized = new CompletableFuture<Void>();
final private SimpleLanguageServer server;
private Supplier<JavaProjectsService> fallback;
final private InitializableJavaProjectsService main;
public JavaProjectsServiceWithFallback(SimpleLanguageServer server, InitializableJavaProjectsService main, Supplier<JavaProjectsService> fallback) {
Assert.isNotNull(fallback);
this.main = main;
this.fallback = Suppliers.memoize(fallback);
this.server = server;
CompletableFuture<Disposable> disposable = new CompletableFuture<Disposable>();
this.server.onInitialized(() -> {
try {
disposable.complete(main.initialize());
initialized.complete(null);
} catch (Throwable e) {
log.info("Fallback classpath provider will be enabled");
disposable.complete(()-> {});
initialized.completeExceptionally(e);
}
});
this.server.onShutdown(() ->
disposable.thenAccept(Disposable::dispose).join()
);
}
@Override
public Optional<IJavaProject> find(TextDocumentIdentifier doc) {
if (initialized.isDone()) {
if (initialized.isCompletedExceptionally()) {
return fallback.get().find(doc);
} else {
return main.find(doc);
}
} else {
log.debug("find => NOT INITIALIZED YET");
}
log.debug("NOT FOUND {} ", doc.getUri());
return Optional.empty();
}
@Override
public void addListener(Listener listener) {
initialized.handle((success, failed) -> {
if (failed!=null) {
fallback.get().addListener(listener);
} else {
main.addListener(listener);
}
return null;
});
}
@Override
public void removeListener(Listener listener) {
initialized.handle((success, failed) -> {
if (failed!=null) {
fallback.get().removeListener(listener);
} else {
main.removeListener(listener);
}
return null;
});
}
@Override
public IJavadocProvider javadocProvider(String projectUri, CPE classpathEntry) {
if (initialized.isDone()) {
if (initialized.isCompletedExceptionally()) {
return fallback.get().javadocProvider(projectUri, classpathEntry);
} else {
return main.javadocProvider(projectUri, classpathEntry);
}
} else {
log.debug("javadoc => NOT INITIALIZED YET");
}
return IJavadocProvider.NULL;
}
}

View File

@@ -21,101 +21,33 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.jandex.JandexIndex.JavadocProviderFactory;
import org.springframework.ide.vscode.commons.java.ClasspathData;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IJavadocProvider;
import org.springframework.ide.vscode.commons.java.JavaProject;
import org.springframework.ide.vscode.commons.javadoc.JdtLsJavadocProvider;
import org.springframework.ide.vscode.commons.languageserver.JavadocParams;
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath.CPE;
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.ExceptionUtil;
import org.springframework.ide.vscode.commons.util.FileObserver;
import org.springframework.ide.vscode.commons.util.UriUtil;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import reactor.core.Disposable;
public class JdtLsProjectCache implements JavaProjectsService {
public class JdtLsProjectCache implements InitializableJavaProjectsService {
private CompletableFuture<Void> initialized = new CompletableFuture<Void>();
private SimpleLanguageServer server;
private Map<String, JavaProject> table = new HashMap<String, JavaProject>();
private Logger log = LoggerFactory.getLogger(JdtLsProjectCache.class);
private List<Listener> listeners = new ArrayList<>();
private final Supplier<JavaProjectsService> fallback;
public JdtLsProjectCache(SimpleLanguageServer server, Supplier<JavaProjectsService> fallback) {
Assert.isNotNull(fallback);
this.fallback = Suppliers.memoize(fallback);
public JdtLsProjectCache(SimpleLanguageServer server) {
this.server = server;
CompletableFuture<Disposable> disposable = new CompletableFuture<Disposable>();
this.server.onInitialized(() -> {
try {
disposable.complete(server.addClasspathListener(new ClasspathListener() {
@Override
public void changed(Event event) {
log.debug("claspath event received {}", event);
initialized.thenRun(() -> {
//log.info("initialized.thenRun block entered");
try {
synchronized (table) {
String uri = UriUtil.normalize(event.projectUri);
log.debug("uri = {}", uri);
if (event.deleted) {
log.debug("event.deleted = true");
JavaProject deleted = table.remove(uri);
if (deleted!=null) {
log.debug("removed from table = true");
notifyDelete(deleted);
} else {
log.warn("Deleted project not removed because uri {} not found in {}", uri, table.keySet());
}
} else {
log.debug("deleted = false");
JdtLsJavadocProvider javadocProvider = new JdtLsJavadocProvider(server.getClient(), uri);
JavaProject newProject = new JavaProject(getFileObserver(), new URI(uri), new ClasspathData(event.name, event.classpath.getEntries()), classpathResource -> javadocProvider);
JavaProject oldProject = table.put(uri, newProject);
if (oldProject != null) {
notifyChanged(newProject);
} else {
notifyCreated(newProject);
}
}
}
} catch (Exception e) {
log.error("", e);
}
});
}
}));
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 FileObserver getFileObserver() {
@@ -132,30 +64,16 @@ public class JdtLsProjectCache implements JavaProjectsService {
@Override
public void addListener(Listener listener) {
initialized.handle((success, failed) -> {
if (failed!=null) {
fallback.get().addListener(listener);
} else {
synchronized (listeners) {
listeners.add(listener);
}
}
return null;
});
synchronized (listeners) {
listeners.add(listener);
}
}
@Override
public void removeListener(Listener listener) {
initialized.handle((success, failed) -> {
if (failed!=null) {
fallback.get().removeListener(listener);
} else {
synchronized (listeners) {
listeners.remove(listener);
}
}
return null;
});
synchronized (listeners) {
listeners.remove(listener);
}
}
private void notifyCreated(JavaProject newProject) {
@@ -215,28 +133,72 @@ public class JdtLsProjectCache implements JavaProjectsService {
public Optional<IJavaProject> find(TextDocumentIdentifier doc) {
String uri = UriUtil.normalize(doc.getUri());
log.debug("find {} ", uri);
if (initialized.isDone()) {
if (initialized.isCompletedExceptionally()) {
log.debug("find {} delegating to fallback", uri);
Optional<IJavaProject> result = fallback.get().find(doc);
log.debug("find => {}", result);
return result;
}
synchronized (table) {
for (Entry<String, JavaProject> e : table.entrySet()) {
String projectUri = e.getKey();
log.debug("projectUri = '{}'", projectUri);
if (UriUtil.contains(projectUri, uri) ) {
log.debug("found {} for {}", e.getValue(), uri);
return Optional.of(e.getValue());
}
synchronized (table) {
for (Entry<String, JavaProject> e : table.entrySet()) {
String projectUri = e.getKey();
log.debug("projectUri = '{}'", projectUri);
if (UriUtil.contains(projectUri, uri)) {
log.debug("found {} for {}", e.getValue(), uri);
return Optional.of(e.getValue());
}
}
} else {
log.debug("find => NOT INITIALIZED YET");
}
log.debug("NOT FOUND {} ", uri);
return Optional.empty();
}
@Override
public IJavadocProvider javadocProvider(String projectUri, CPE classpathEntry) {
return new JdtLsJavadocProvider(server.getClient(), projectUri);
}
@Override
public Disposable initialize() throws Exception {
try {
return server.addClasspathListener(new ClasspathListener() {
@Override
public void changed(Event event) {
log.debug("claspath event received {}", event);
server.onInitialized(() -> {
//log.info("initialized.thenRun block entered");
try {
synchronized (table) {
String uri = UriUtil.normalize(event.projectUri);
log.debug("uri = {}", uri);
if (event.deleted) {
log.debug("event.deleted = true");
JavaProject deleted = table.remove(uri);
if (deleted!=null) {
log.debug("removed from table = true");
notifyDelete(deleted);
} else {
log.warn("Deleted project not removed because uri {} not found in {}", uri, table.keySet());
}
} else {
log.debug("deleted = false");
JavaProject newProject = new JavaProject(getFileObserver(), new URI(uri), new ClasspathData(event.name, event.classpath.getEntries()), JdtLsProjectCache.this);
JavaProject oldProject = table.put(uri, newProject);
if (oldProject != null) {
notifyChanged(newProject);
} else {
notifyCreated(newProject);
}
}
}
} catch (Exception e) {
log.error("", e);
}
});
}
});
} catch (Throwable t) {
if (isNoJdtError(t)) {
log.info("JDT Language Server not available. Fallback classpath provider will be used instead.");
} else if (isOldJdt(t)) {
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.", t);
}
throw t;
}
}
}

View File

@@ -21,11 +21,11 @@ import java.util.Optional;
import org.junit.Test;
import org.springframework.ide.vscode.boot.java.links.VSCodeSourceLinks;
import org.springframework.ide.vscode.commons.javadoc.JavaDocProviders;
import org.springframework.ide.vscode.commons.maven.MavenBuilder;
import org.springframework.ide.vscode.commons.maven.MavenCore;
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
import org.springframework.ide.vscode.commons.util.BasicFileObserver;
import org.springframework.ide.vscode.commons.util.FileObserver;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
@@ -45,8 +45,10 @@ public class VSCodeSourceLinksTest {
public MavenJavaProject load(String projectName) throws Exception {
Path testProjectPath = Paths.get(VSCodeSourceLinksTest.class.getResource("/test-projects/" + projectName).toURI());
MavenBuilder.newBuilder(testProjectPath).clean().pack().javadoc().skipTests().execute();
return MavenJavaProject.create(new BasicFileObserver(), MavenCore.getDefault(), testProjectPath.resolve(MavenCore.POM_XML).toFile());
}
return MavenJavaProject.create(new BasicFileObserver(), MavenCore.getDefault(),
testProjectPath.resolve(MavenCore.POM_XML).toFile(),
(uri, cpe) -> JavaDocProviders.createFor(cpe));
}
});

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016, 2017 Pivotal, Inc.
* Copyright (c) 2016, 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
@@ -22,6 +22,7 @@ import org.apache.commons.io.FileUtils;
import org.springframework.ide.vscode.commons.java.DelegatingCachedClasspath;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.LegacyJavaProject;
import org.springframework.ide.vscode.commons.javadoc.JavaDocProviders;
import org.springframework.ide.vscode.commons.maven.MavenBuilder;
import org.springframework.ide.vscode.commons.maven.MavenCore;
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
@@ -89,7 +90,8 @@ public class ProjectsHarness {
}
public static final IJavaProject dummyProject() throws URISyntaxException {
return new LegacyJavaProject(new BasicFileObserver(), new URI("file:///someplace/nonexistent"), null, new DelegatingCachedClasspath(() -> null, null));
return new LegacyJavaProject(new BasicFileObserver(), new URI("file:///someplace/nonexistent"), null,
new DelegatingCachedClasspath(() -> null, null), (uri, cpe) -> JavaDocProviders.createFor(cpe));
}
private ProjectsHarness(FileObserver fileObserver) {
@@ -111,7 +113,8 @@ public class ProjectsHarness {
switch (type) {
case MAVEN:
MavenBuilder.newBuilder(testProjectPath).clean().pack().javadoc().skipTests().execute();
return MavenJavaProject.create(fileObserver, MavenCore.getDefault(), testProjectPath.resolve(MavenCore.POM_XML).toFile());
return MavenJavaProject.create(fileObserver, MavenCore.getDefault(),
testProjectPath.resolve(MavenCore.POM_XML).toFile(), (uri, cpe) -> JavaDocProviders.createFor(cpe));
default:
throw new IllegalStateException("Bug!!! Missing case");
}