From 08c1cd33ef9f213f5b28ded78f5a932d7e0f02b0 Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Fri, 17 Nov 2017 18:32:12 -0500 Subject: [PATCH] PT #152867134 Progress and Error feedback for Maven/Gradle project load --- .../java/BootJavaLanguageServerParams.java | 11 +- .../vscode/boot/java/utils/SpringIndexer.java | 7 +- .../BootPropertiesLanguageServerParams.java | 16 +- .../commons/commons-gradle/pom.xml | 13 + .../commons/gradle/GradleJavaProject.java | 23 +- .../commons/gradle/GradleProjectCache.java | 24 +- .../gradle/GradleProjectClasspath.java | 47 +--- .../commons/gradle/GradleProjectTest.java | 37 ++- .../commons/commons-java/pom.xml | 5 + .../java/DelegatingCachedClasspath.java | 56 ++-- .../java/AbstractFileToProjectCache.java | 49 +++- .../java/AbstractJavaProjectCache.java | 9 +- .../languageserver/DiagnosticService.java | 30 ++ .../languageserver/Sts4LanguageServer.java | 43 +++ .../util/SimpleLanguageServer.java | 14 +- .../commons/commons-maven/pom.xml | 14 + .../commons/maven/java/MavenJavaProject.java | 25 +- .../commons/maven/java/MavenProjectCache.java | 24 +- .../maven/java/MavenProjectClasspath.java | 29 +- .../JavaProjectWithClasspathFileCache.java | 6 +- .../commons/maven/MavenProjectCacheTest.java | 256 ++++++++++++++++++ .../maven/MavenProjectManagerTest.java | 104 ------- 22 files changed, 581 insertions(+), 261 deletions(-) create mode 100644 headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/DiagnosticService.java create mode 100644 headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/Sts4LanguageServer.java create mode 100644 headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/MavenProjectCacheTest.java delete mode 100644 headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/MavenProjectManagerTest.java diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerParams.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerParams.java index 0951d80e9..cb61deece 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerParams.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerParams.java @@ -32,7 +32,6 @@ import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguage import org.springframework.ide.vscode.commons.maven.MavenCore; import org.springframework.ide.vscode.commons.maven.java.MavenProjectCache; import org.springframework.ide.vscode.commons.maven.java.MavenProjectFinder; -import org.springframework.ide.vscode.commons.util.FileObserver; public class BootJavaLanguageServerParams { @@ -60,12 +59,11 @@ public class BootJavaLanguageServerParams { public static LSFactory createDefault() { return (SimpleLanguageServer server) -> { // Initialize project finders, project caches and project observers - FileObserver fileObserver = server.getWorkspaceService().getFileObserver(); CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder(); - MavenProjectCache mavenProjectCache = new MavenProjectCache(fileObserver, MavenCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER)); + MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER)); javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache)); - GradleProjectCache gradleProjectCache = new GradleProjectCache(fileObserver, GradleCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER)); + 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)); @@ -83,13 +81,12 @@ public class BootJavaLanguageServerParams { public static LSFactory createTestDefault() { return (SimpleLanguageServer server) -> { // Initialize project finders, project caches and project observers - FileObserver fileObserver = server.getWorkspaceService().getFileObserver(); CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder(); - MavenProjectCache mavenProjectCache = new MavenProjectCache(fileObserver, MavenCore.getDefault(), false, null); + MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), false, null); mavenProjectCache.setAlwaysFireEventOnFileChanged(true); javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache)); - GradleProjectCache gradleProjectCache = new GradleProjectCache(fileObserver, GradleCore.getDefault(), false, null); + GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), false, null); gradleProjectCache.setAlwaysFireEventOnFileChanged(true); javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache)); diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java index 3390c998b..9bc618867 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java @@ -183,9 +183,8 @@ public class SpringIndexer { if (server.getProjectObserver() != null) { server.getProjectObserver().removeListener(projectListener); } - } - catch (Exception e) { - e.printStackTrace(); + } catch (Exception e) { + Log.log(e); } } @@ -223,7 +222,7 @@ public class SpringIndexer { return this.symbols; } } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); + // ignore } } return null; diff --git a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/BootPropertiesLanguageServerParams.java b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/BootPropertiesLanguageServerParams.java index acfc4ba88..57645d6a4 100644 --- a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/BootPropertiesLanguageServerParams.java +++ b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/BootPropertiesLanguageServerParams.java @@ -32,7 +32,6 @@ import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguage import org.springframework.ide.vscode.commons.maven.MavenCore; import org.springframework.ide.vscode.commons.maven.java.MavenProjectCache; import org.springframework.ide.vscode.commons.maven.java.MavenProjectFinder; -import org.springframework.ide.vscode.commons.util.FileObserver; import org.springframework.ide.vscode.commons.util.text.IDocument; /** @@ -64,12 +63,11 @@ public class BootPropertiesLanguageServerParams { public static LSFactory createDefault() { return (SimpleLanguageServer server) -> { // Initialize project finders, project caches and project observers - FileObserver fileObserver = server.getWorkspaceService().getFileObserver(); CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder(); - MavenProjectCache mavenProjectCache = new MavenProjectCache(fileObserver, MavenCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER)); + MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER)); javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache)); - GradleProjectCache gradleProjectCache = new GradleProjectCache(fileObserver, GradleCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER)); + 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)); @@ -89,13 +87,12 @@ public class BootPropertiesLanguageServerParams { public static LSFactory createTestDefault(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider) { return (SimpleLanguageServer server) -> { // Initialize project finders, project caches and project observers - FileObserver fileObserver = server.getWorkspaceService().getFileObserver(); CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder(); - MavenProjectCache mavenProjectCache = new MavenProjectCache(fileObserver, MavenCore.getDefault(), false, null); + MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), false, null); mavenProjectCache.setAlwaysFireEventOnFileChanged(true); javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache)); - GradleProjectCache gradleProjectCache = new GradleProjectCache(fileObserver, GradleCore.getDefault(), false, null); + GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), false, null); gradleProjectCache.setAlwaysFireEventOnFileChanged(true); javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache)); @@ -113,13 +110,12 @@ public class BootPropertiesLanguageServerParams { public static LSFactory createTestDefault() { return (SimpleLanguageServer server) -> { // Initialize project finders, project caches and project observers - FileObserver fileObserver = server.getWorkspaceService().getFileObserver(); CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder(); - MavenProjectCache mavenProjectCache = new MavenProjectCache(fileObserver, MavenCore.getDefault(), false, null); + MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), false, null); mavenProjectCache.setAlwaysFireEventOnFileChanged(true); javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache)); - GradleProjectCache gradleProjectCache = new GradleProjectCache(fileObserver, GradleCore.getDefault(), false, null); + GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), false, null); gradleProjectCache.setAlwaysFireEventOnFileChanged(true); javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache)); diff --git a/headless-services/commons/commons-gradle/pom.xml b/headless-services/commons/commons-gradle/pom.xml index b1b179b9d..4669a17eb 100644 --- a/headless-services/commons/commons-gradle/pom.xml +++ b/headless-services/commons/commons-gradle/pom.xml @@ -36,6 +36,19 @@ gradle-tooling-api ${gradle-tooling.version} + + + org.springframework.ide.vscode + language-server-test-harness + ${project.version} + test + + + org.mockito + mockito-all + ${mockito-version} + test + \ No newline at end of file diff --git a/headless-services/commons/commons-gradle/src/main/java/org/springframework/ide/vscode/commons/gradle/GradleJavaProject.java b/headless-services/commons/commons-gradle/src/main/java/org/springframework/ide/vscode/commons/gradle/GradleJavaProject.java index 474529bc0..669bb8e35 100644 --- a/headless-services/commons/commons-gradle/src/main/java/org/springframework/ide/vscode/commons/gradle/GradleJavaProject.java +++ b/headless-services/commons/commons-gradle/src/main/java/org/springframework/ide/vscode/commons/gradle/GradleJavaProject.java @@ -15,6 +15,7 @@ import java.nio.file.Path; import org.springframework.ide.vscode.commons.java.AbstractJavaProject; import org.springframework.ide.vscode.commons.java.DelegatingCachedClasspath; +import org.springframework.ide.vscode.commons.util.Log; /** * Implementation of Gradle Java project @@ -36,6 +37,26 @@ public class GradleJavaProject extends AbstractJavaProject { ); } + public GradleJavaProject(GradleCore gradle, File projectDir) { + this(gradle, projectDir, null); + if (!classpath.isCached()) { + try { + classpath.update(); + } catch (Exception e) { + Log.log(e); + } + } + } + + @Override + public String getElementName() { + if (classpath.getName() == null) { + return projectDir.getName(); + } else { + return super.getElementName(); + } + } + public File getLocation() { return projectDir; } @@ -45,7 +66,7 @@ public class GradleJavaProject extends AbstractJavaProject { return classpath; } - boolean update() { + boolean update() throws Exception { return classpath.update(); } diff --git a/headless-services/commons/commons-gradle/src/main/java/org/springframework/ide/vscode/commons/gradle/GradleProjectCache.java b/headless-services/commons/commons-gradle/src/main/java/org/springframework/ide/vscode/commons/gradle/GradleProjectCache.java index 94c9fbbb8..110d1409b 100644 --- a/headless-services/commons/commons-gradle/src/main/java/org/springframework/ide/vscode/commons/gradle/GradleProjectCache.java +++ b/headless-services/commons/commons-gradle/src/main/java/org/springframework/ide/vscode/commons/gradle/GradleProjectCache.java @@ -13,8 +13,11 @@ package org.springframework.ide.vscode.commons.gradle; import java.io.File; import java.nio.file.Path; +import org.eclipse.lsp4j.MessageParams; +import org.eclipse.lsp4j.MessageType; +import org.springframework.ide.vscode.commons.languageserver.Sts4LanguageServer; import org.springframework.ide.vscode.commons.languageserver.java.AbstractFileToProjectCache; -import org.springframework.ide.vscode.commons.util.FileObserver; +import org.springframework.ide.vscode.commons.languageserver.util.ShowMessageException; /** * Tests whether document belongs to a Gradle project @@ -26,24 +29,29 @@ public class GradleProjectCache extends AbstractFileToProjectCache gradleProject; - private Supplier buildEnvironment; + private EclipseProject project; + private BuildEnvironment buildEnvironment; - public GradleProjectClasspath(GradleCore gradle, File projectDir) { + public GradleProjectClasspath(GradleCore gradle, File projectDir) throws GradleException { super(); - this.gradleProject = Suppliers.memoize(() -> { - try { - return gradle.getModel(projectDir, EclipseProject.class); - } catch (GradleException e) { - Log.log(e); - return null; - } - }); - this.buildEnvironment = Suppliers.memoize(() -> { - try { - return gradle.getModel(projectDir, BuildEnvironment.class); - } catch (GradleException e) { - Log.log(e); - return null; - } - }); + this.project = gradle.getModel(projectDir, EclipseProject.class); + this.buildEnvironment = gradle.getModel(projectDir, BuildEnvironment.class); } @Override @@ -92,7 +76,7 @@ public class GradleProjectClasspath extends JandexClasspath { } public EclipseProject getRootProject() { - EclipseProject root = this.gradleProject.get(); + EclipseProject root = project; if (root == null) { return root; } @@ -105,7 +89,6 @@ public class GradleProjectClasspath extends JandexClasspath { @Override public ImmutableList getClasspathEntries() throws Exception { EclipseProject root = getRootProject(); - EclipseProject project = gradleProject.get(); if (project == null) { return ImmutableList.of(); } else { @@ -126,7 +109,6 @@ public class GradleProjectClasspath extends JandexClasspath { @Override public ImmutableList getClasspathResources() { - EclipseProject project = gradleProject.get(); if (project == null) { return ImmutableList.of(); } else { @@ -145,22 +127,19 @@ public class GradleProjectClasspath extends JandexClasspath { } public Path getOutputFolder() { - EclipseProject project = gradleProject.get(); return project == null ? null : project.getProjectDirectory().toPath().resolve(project.getOutputLocation().getPath()); } public String getName() { - EclipseProject project = gradleProject.get(); return project == null ? null : project.getName(); } public boolean exists() { - return gradleProject != null; + return project != null; } @Override protected IJavadocProvider createParserJavadocProvider(File classpathResource) { - EclipseProject project = gradleProject.get(); if (project != null) { if (classpathResource.isDirectory()) { Optional classpathFolder = project.getSourceDirectories().stream() @@ -187,18 +166,18 @@ public class GradleProjectClasspath extends JandexClasspath { } public String getGradleVersion() throws GradleException { - if (buildEnvironment.get() == null) { + if (buildEnvironment == null) { throw new GradleException(new Exception("Cannot find Gradle version")); } else { - return buildEnvironment.get().getGradle().getGradleVersion(); + return buildEnvironment.getGradle().getGradleVersion(); } } public File getGradleHome() throws GradleException { - if (buildEnvironment.get() == null) { + if (buildEnvironment == null) { throw new GradleException(new Exception("Cannot find Gradle home folder")); } else { - return buildEnvironment.get().getGradle().getGradleUserHome(); + return buildEnvironment.getGradle().getGradleUserHome(); } } @@ -218,10 +197,10 @@ public class GradleProjectClasspath extends JandexClasspath { } private String getJavaHome() { - if (buildEnvironment.get() == null) { + if (buildEnvironment == null) { return System.getProperty(JAVA_HOME); } else { - return buildEnvironment.get().getJava().getJavaHome().toString(); + return buildEnvironment.getJava().getJavaHome().toString(); } } diff --git a/headless-services/commons/commons-gradle/src/test/java/org/springframework/ide/vscode/commons/gradle/GradleProjectTest.java b/headless-services/commons/commons-gradle/src/test/java/org/springframework/ide/vscode/commons/gradle/GradleProjectTest.java index 4e207ac69..8e58dc89e 100644 --- a/headless-services/commons/commons-gradle/src/test/java/org/springframework/ide/vscode/commons/gradle/GradleProjectTest.java +++ b/headless-services/commons/commons-gradle/src/test/java/org/springframework/ide/vscode/commons/gradle/GradleProjectTest.java @@ -15,6 +15,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.io.File; import java.io.FileWriter; @@ -26,9 +28,12 @@ import java.util.List; import java.util.Optional; import org.assertj.core.util.Files; +import org.junit.Before; import org.junit.Test; import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.Sts4LanguageServer; import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver.Listener; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleWorkspaceService; import org.springframework.ide.vscode.commons.util.BasicFileObserver; import com.google.common.collect.ImmutableList; @@ -41,6 +46,18 @@ import com.google.common.collect.ImmutableList; */ public class GradleProjectTest { + private Sts4LanguageServer server; + private BasicFileObserver fileObserver; + + @Before + public void setup() throws Exception { + fileObserver = new BasicFileObserver(); + server = mock(Sts4LanguageServer.class); + SimpleWorkspaceService workspaceService = mock(SimpleWorkspaceService.class); + when(workspaceService.getFileObserver()).thenReturn(fileObserver); + when(server.getWorkspaceService()).thenReturn(workspaceService); + } + private static void writeContent(File file, String content) throws IOException { FileWriter writer = null; try { @@ -53,9 +70,7 @@ public class GradleProjectTest { private GradleJavaProject getGradleProject(String projectName) throws Exception { Path testProjectPath = Paths.get(GradleProjectTest.class.getResource("/" + projectName).toURI()); - GradleJavaProject gradleJavaProject = new GradleJavaProject(GradleCore.getDefault(), testProjectPath.toFile(), null); - gradleJavaProject.update(); - return gradleJavaProject; + return new GradleJavaProject(GradleCore.getDefault(), testProjectPath.toFile()); } @Test @@ -86,15 +101,9 @@ public class GradleProjectTest { String gradelFileContents = Files.contentOf(gradleFile, Charset.defaultCharset()); try { - BasicFileObserver fileObserver = new BasicFileObserver(); - GradleProjectCache manager = new GradleProjectCache(fileObserver, GradleCore.getDefault(), false, null); + GradleProjectCache manager = new GradleProjectCache(server, GradleCore.getDefault(), false, null); IJavaProject[] projectChanged = new IJavaProject[] { null }; IJavaProject[] projectDeleted = new IJavaProject[] { null }; - - // Get the project from cache - GradleJavaProject cachedProject = manager.project(gradleFile); - assertNotNull(cachedProject); - manager.addListener(new Listener() { @Override public void created(IJavaProject project) {} @@ -109,6 +118,10 @@ public class GradleProjectTest { } }); + // Get the project from cache + GradleJavaProject cachedProject = manager.project(gradleFile); + assertNotNull(cachedProject); + ImmutableList calculatedClassPath = cachedProject.getClasspath().getClasspathEntries(); assertEquals(48, calculatedClassPath.size()); @@ -132,7 +145,7 @@ public class GradleProjectTest { @Test public void findGradleProjectWithStandardBuildFile() throws Exception { - GradleProjectFinder finder = new GradleProjectFinder(new GradleProjectCache(new BasicFileObserver(), GradleCore.getDefault(), false, null)); + GradleProjectFinder finder = new GradleProjectFinder(new GradleProjectCache(server, GradleCore.getDefault(), false, null)); File sourceFile = new File(GradleProjectTest.class.getResource("/test-app-1/src/main/java/Library.java").toURI()); Optional project = finder.find(sourceFile); assertTrue(project.isPresent()); @@ -143,7 +156,7 @@ public class GradleProjectTest { @Test public void findGradleProjectWithNonStandardBuildFile() throws Exception { - GradleProjectFinder finder = new GradleProjectFinder(new GradleProjectCache(new BasicFileObserver(), GradleCore.getDefault(), false, null)); + GradleProjectFinder finder = new GradleProjectFinder(new GradleProjectCache(server, GradleCore.getDefault(), false, null)); File sourceFile = new File(GradleProjectTest.class.getResource("/test-app-2/src/main/java/Library.java").toURI()); Optional project = finder.find(sourceFile); assertTrue(project.isPresent()); diff --git a/headless-services/commons/commons-java/pom.xml b/headless-services/commons/commons-java/pom.xml index 52f92d2ea..8cc8509a5 100644 --- a/headless-services/commons/commons-java/pom.xml +++ b/headless-services/commons/commons-java/pom.xml @@ -24,6 +24,11 @@ commons-util ${project.version} + + org.springframework.ide.vscode + commons-language-server + ${project.version} + org.jboss jandex diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/DelegatingCachedClasspath.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/DelegatingCachedClasspath.java index 4e2e4824e..addf5d95f 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/DelegatingCachedClasspath.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/DelegatingCachedClasspath.java @@ -19,6 +19,7 @@ import java.nio.file.Path; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -29,7 +30,6 @@ import org.json.JSONTokener; import org.springframework.ide.vscode.commons.util.Log; import com.google.common.base.Objects; -import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import reactor.core.publisher.Flux; @@ -93,20 +93,17 @@ public class DelegatingCachedClasspath implements IClasspa } private AtomicReference cachedData; - private Supplier delegateCreator; + private Callable delegateCreator; private AtomicReference cachedDelegate; final private File cacheFile; - public DelegatingCachedClasspath(Supplier delegateCreator, File cacheFile) { + public DelegatingCachedClasspath(Callable delegateCreator, File cacheFile) { super(); this.cacheFile = cacheFile; - this.cachedDelegate = new AtomicReference<>(delegateCreator.get()); + this.cachedDelegate = new AtomicReference<>(null); this.cachedData = new AtomicReference<>(init()); this.delegateCreator = delegateCreator; - if (!isCached()) { - update(); - } } public T delegate() { @@ -197,34 +194,43 @@ public class DelegatingCachedClasspath implements IClasspa } } - public boolean update() { - final ClasspathData newData = createClasspathData(); - if (!Objects.equal(cachedData.get(), newData)) { - cachedData.set(newData); - persistCachedData(newData); - return true; + public boolean update() throws Exception { + try { + final ClasspathData newData = createClasspathData(); + if (!Objects.equal(cachedData.get(), newData)) { + cachedData.set(newData); + persistCachedData(newData); + return true; + } + return false; + } catch (Exception e) { + cachedData.set(new ClasspathData(null, Collections.emptySet(), Collections.emptySet(), null)); + throw e; } - return false; } @Override public boolean exists() { - return cachedDelegate.get().exists(); + T t = cachedDelegate.get(); + return t != null && t.exists(); } @Override public IType findType(String fqName) { - return cachedDelegate.get().findType(fqName); + T t = cachedDelegate.get(); + return t == null ? null : t.findType(fqName); } @Override public Flux> fuzzySearchTypes(String searchTerm, Predicate typeFilter) { - return cachedDelegate.get().fuzzySearchTypes(searchTerm, typeFilter); + T t = cachedDelegate.get(); + return t == null ? Flux.empty() : t.fuzzySearchTypes(searchTerm, typeFilter); } @Override public Flux> fuzzySearchPackages(String searchTerm) { - return cachedDelegate.get().fuzzySearchPackages(searchTerm); + T t = cachedDelegate.get(); + return t == null ? Flux.empty() : t.fuzzySearchPackages(searchTerm); } @Override @@ -232,16 +238,12 @@ public class DelegatingCachedClasspath implements IClasspa return cachedDelegate.get().allSubtypesOf(type); } - protected ClasspathData createClasspathData() { - T newDelegate = delegateCreator.get(); + protected ClasspathData createClasspathData() throws Exception { + T newDelegate = delegateCreator.call(); cachedDelegate.set(newDelegate); - try { - LinkedHashSet classpathEntries = new LinkedHashSet<>(newDelegate.getClasspathEntries()); - return new ClasspathData(newDelegate.getName(), classpathEntries, new LinkedHashSet<>(newDelegate.getClasspathResources()), newDelegate.getOutputFolder()); - } catch (Exception e) { - Log.log(e); - return new ClasspathData(newDelegate.getName(), Collections.emptySet(), new LinkedHashSet<>(newDelegate.getClasspathResources()), newDelegate.getOutputFolder()); - } + LinkedHashSet classpathEntries = new LinkedHashSet<>(newDelegate.getClasspathEntries()); + return new ClasspathData(newDelegate.getName(), classpathEntries, + new LinkedHashSet<>(newDelegate.getClasspathResources()), newDelegate.getOutputFolder()); } } diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/AbstractFileToProjectCache.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/AbstractFileToProjectCache.java index abd2f3da5..a8e106f1b 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/AbstractFileToProjectCache.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/AbstractFileToProjectCache.java @@ -15,9 +15,11 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.util.FileObserver; +import org.springframework.ide.vscode.commons.languageserver.ProgressService; +import org.springframework.ide.vscode.commons.languageserver.Sts4LanguageServer; /** * Cache for java projects. The key for the cache is a "project" specific file @@ -32,24 +34,26 @@ public abstract class AbstractFileToProjectCache

extends private String deleteSubscription; protected boolean asyncUpdate; protected final Path projectCacheFolder; - private boolean alwaysFireEventOnFileChanged; + private boolean alwaysFireEventOnUpdate; - public AbstractFileToProjectCache(FileObserver fileObserver, boolean asyncUpdate, Path projectCacheFolder) { - super(fileObserver); + private static AtomicInteger progressIdCt = new AtomicInteger(0); + + public AbstractFileToProjectCache(Sts4LanguageServer server, boolean asyncUpdate, Path projectCacheFolder) { + super(server); this.projectCacheFolder = projectCacheFolder; this.asyncUpdate = asyncUpdate; } - final public void setAlwaysFireEventOnFileChanged(boolean alwaysFireEventOnFileChanged) { - this.alwaysFireEventOnFileChanged = alwaysFireEventOnFileChanged; + final public void setAlwaysFireEventOnFileChanged(boolean alwaysFireEventOnUpdate) { + this.alwaysFireEventOnUpdate = alwaysFireEventOnUpdate; } @Override protected void attachListeners(File file, P project) { super.attachListeners(file, project); List globPattern = Arrays.asList(file.toString()); - changeSubscription = getFileObserver().onFileChanged(globPattern, (uri) -> performUpdate(project, asyncUpdate)); + changeSubscription = getFileObserver().onFileChanged(globPattern, (uri) -> performUpdate(project, asyncUpdate, true)); deleteSubscription = getFileObserver().onFileDeleted(globPattern, (uri) -> { cache.invalidate(file); notifyProjectDeleted(project); @@ -58,20 +62,37 @@ public abstract class AbstractFileToProjectCache

extends }); } - final protected void performUpdate(P project, boolean async) { + final protected void performUpdate(P project, boolean async, boolean notify) { + final String taskId = getProgressId(); + final ProgressService progressService = server.getProgressService(); + if (progressService != null) { + progressService.progressEvent(taskId, "Updating data for project `" + project.getElementName() + "'"); + } if (async) { - CompletableFuture.supplyAsync(() -> update(project)).thenAccept((changed) -> { - if (changed || alwaysFireEventOnFileChanged) { - notifyProjectChanged(project); - } - }); + CompletableFuture.supplyAsync(() -> update(project)).thenAccept((changed) -> afterUpdate(project, changed, notify, taskId)); } else { - if (update(project) || alwaysFireEventOnFileChanged) { + boolean changed = update(project); + afterUpdate(project, changed, notify, taskId); + } + } + + private void afterUpdate(P project, boolean changed, boolean notify, String taskId) { + final ProgressService progressService = server.getProgressService(); + if (progressService != null) { + progressService.progressEvent(taskId, null); + } + if (changed || alwaysFireEventOnUpdate) { + if (notify) { notifyProjectChanged(project); } } } + private static String getProgressId() { + return AbstractFileToProjectCache.class.getName()+ (progressIdCt.incrementAndGet()); + } + + abstract protected boolean update(P project); } diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/AbstractJavaProjectCache.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/AbstractJavaProjectCache.java index b237d9b8c..d0ca161f7 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/AbstractJavaProjectCache.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/AbstractJavaProjectCache.java @@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.languageserver.java; import java.util.concurrent.ExecutionException; import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.Sts4LanguageServer; import org.springframework.ide.vscode.commons.util.FileObserver; import org.springframework.ide.vscode.commons.util.ListenerList; import org.springframework.ide.vscode.commons.util.Log; @@ -30,14 +31,14 @@ import com.google.common.cache.CacheBuilder; */ public abstract class AbstractJavaProjectCache implements JavaProjectCache { - private FileObserver fileObserver; + protected Sts4LanguageServer server; private ListenerList listeners = new ListenerList<>(); protected Cache cache = CacheBuilder.newBuilder().build(); - public AbstractJavaProjectCache(FileObserver fileObserver) { - this.fileObserver = fileObserver; + public AbstractJavaProjectCache(Sts4LanguageServer server) { + this.server = server; } @Override @@ -90,6 +91,6 @@ public abstract class AbstractJavaProjectCache implem } final protected FileObserver getFileObserver() { - return fileObserver; + return server.getWorkspaceService().getFileObserver(); } } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/DiagnosticService.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/DiagnosticService.java new file mode 100644 index 000000000..851890f59 --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/DiagnosticService.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2017 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.commons.languageserver; + +import org.springframework.ide.vscode.commons.languageserver.util.ShowMessageException; + +/** + * Reports error/warnings from the LS to the client via LSP message + * + * @author Alex Boyko + * + */ +@FunctionalInterface +public interface DiagnosticService { + + /** + * Sends the error/warning message to the client + * @param message + */ + void diagnosticEvent(ShowMessageException message); + +} diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/Sts4LanguageServer.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/Sts4LanguageServer.java new file mode 100644 index 000000000..7d40d7f3f --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/Sts4LanguageServer.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2017 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.commons.languageserver; + +import org.eclipse.lsp4j.services.LanguageServer; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleWorkspaceService; + +/** + * STS4 language server + * + * @author Alex Boyko + * + */ +public interface Sts4LanguageServer extends LanguageServer { + + @Override + SimpleTextDocumentService getTextDocumentService(); + + @Override + SimpleWorkspaceService getWorkspaceService(); + + /** + * Progress Service to report progress info from LS to the client + * @return progress service instance + */ + ProgressService getProgressService(); + + /** + * Diagnostic service to report errors/warnings from LS to the client + * @return + */ + DiagnosticService getDiagnosticService(); + +} diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java index 6c097efd9..533ae6bf3 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java @@ -38,10 +38,11 @@ import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.TextDocumentSyncKind; import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageClientAware; -import org.eclipse.lsp4j.services.LanguageServer; +import org.springframework.ide.vscode.commons.languageserver.DiagnosticService; import org.springframework.ide.vscode.commons.languageserver.ProgressParams; import org.springframework.ide.vscode.commons.languageserver.ProgressService; import org.springframework.ide.vscode.commons.languageserver.STS4LanguageClient; +import org.springframework.ide.vscode.commons.languageserver.Sts4LanguageServer; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter.LazyCompletionResolver; @@ -75,7 +76,7 @@ import reactor.core.scheduler.Schedulers; * here so we can try to keep the subclass itself more 'clutter free' and focus on * what its really doing and not the 'wiring and plumbing'. */ -public abstract class SimpleLanguageServer implements LanguageServer, LanguageClientAware, ServiceNotificationsClient, WorkspaceFoldersProposedService { +public abstract class SimpleLanguageServer implements Sts4LanguageServer, LanguageClientAware, ServiceNotificationsClient, WorkspaceFoldersProposedService { private static final Scheduler RECONCILER_SCHEDULER = Schedulers.newSingle("Reconciler"); @@ -96,6 +97,8 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl } }; + private DiagnosticService diagnosticService = message -> onError(null, message); + private CompletableFuture busyReconcile = CompletableFuture.completedFuture(null); private QuickfixRegistry quickfixRegistry; @@ -514,6 +517,7 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl return client; } + @Override public ProgressService getProgressService() { return progressService; } @@ -532,4 +536,10 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl getWorkspaceService().didChangeWorkspaceFolders(params); } + + @Override + public DiagnosticService getDiagnosticService() { + return diagnosticService; + } + } diff --git a/headless-services/commons/commons-maven/pom.xml b/headless-services/commons/commons-maven/pom.xml index 4fe5a7ca9..1b6512859 100644 --- a/headless-services/commons/commons-maven/pom.xml +++ b/headless-services/commons/commons-maven/pom.xml @@ -66,6 +66,20 @@ ${project.version} + + + org.springframework.ide.vscode + language-server-test-harness + ${project.version} + test + + + org.mockito + mockito-all + ${mockito-version} + test + + diff --git a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java index 5c2c868f9..bf9a645f9 100644 --- a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java +++ b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java @@ -16,6 +16,7 @@ import java.nio.file.Path; import org.springframework.ide.vscode.commons.java.AbstractJavaProject; import org.springframework.ide.vscode.commons.java.DelegatingCachedClasspath; import org.springframework.ide.vscode.commons.maven.MavenCore; +import org.springframework.ide.vscode.commons.util.Log; /** * Wrapper for Maven Core project @@ -26,9 +27,11 @@ import org.springframework.ide.vscode.commons.maven.MavenCore; public class MavenJavaProject extends AbstractJavaProject { private DelegatingCachedClasspath classpath; + private File pom; public MavenJavaProject(MavenCore maven, File pom, Path projectDataCache) { super(projectDataCache); + this.pom = pom; this.classpath = new DelegatingCachedClasspath<>( () -> new MavenProjectClasspath(maven, pom), projectDataCache == null ? null : projectDataCache.resolve(DelegatingCachedClasspath.CLASSPATH_DATA_CACHE_FILE).toFile() @@ -37,6 +40,22 @@ public class MavenJavaProject extends AbstractJavaProject { public MavenJavaProject(MavenCore maven, File pom) { this(maven, pom, null); + if (!classpath.isCached()) { + try { + classpath.update(); + } catch (Exception e) { + Log.log(e); + } + } + } + + @Override + public String getElementName() { + if (classpath.getName() == null) { + return pom.getParentFile().getName(); + } else { + return super.getElementName(); + } } @Override @@ -44,10 +63,14 @@ public class MavenJavaProject extends AbstractJavaProject { return classpath; } - boolean update() { + boolean update() throws Exception { return classpath.update(); } + public File pom() { + return pom; + } + @Override public String toString() { return "MavenJavaProject("+classpath.getName()+")"; diff --git a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectCache.java b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectCache.java index 227964cb4..7a6c1bba7 100644 --- a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectCache.java +++ b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectCache.java @@ -13,9 +13,12 @@ package org.springframework.ide.vscode.commons.maven.java; import java.io.File; import java.nio.file.Path; +import org.eclipse.lsp4j.MessageParams; +import org.eclipse.lsp4j.MessageType; +import org.springframework.ide.vscode.commons.languageserver.Sts4LanguageServer; import org.springframework.ide.vscode.commons.languageserver.java.AbstractFileToProjectCache; +import org.springframework.ide.vscode.commons.languageserver.util.ShowMessageException; import org.springframework.ide.vscode.commons.maven.MavenCore; -import org.springframework.ide.vscode.commons.util.FileObserver; /** * Cache for Maven projects @@ -26,23 +29,28 @@ public class MavenProjectCache extends AbstractFileToProjectCache projectSupplier; + private MavenProject project; - MavenProjectClasspath(MavenCore maven, File pom) { + MavenProjectClasspath(MavenCore maven, File pom) throws MavenException { super(); this.maven = maven; this.pom = pom; - this.projectSupplier = Suppliers.memoize(() -> createMavenProject()); + this.project = createMavenProject(); } @Override @@ -63,19 +62,13 @@ public class MavenProjectClasspath extends JandexClasspath { return new JandexIndex[] { maven.getJavaIndexForJreLibs() }; } - private final MavenProject createMavenProject() { + private final MavenProject createMavenProject() throws MavenException { try { // Read with resolved dependencies return maven.readProject(pom, true); } catch (MavenException e) { Log.log(e); - try { - // Try without resolving dependencies - just read the XML - return maven.readProject(pom, false); - } catch (MavenException e1) { - Log.log(e); - return null; - } + return maven.readProject(pom, false); } } @@ -92,7 +85,6 @@ public class MavenProjectClasspath extends JandexClasspath { } public String getName() { - MavenProject project = projectSupplier.get(); return project == null ? null : project.getName(); } @@ -107,12 +99,10 @@ public class MavenProjectClasspath extends JandexClasspath { } private Set projectDependencies() { - MavenProject project = projectSupplier.get(); return project == null ? Collections.emptySet() : project.getArtifacts(); } private List projectOutput() { - MavenProject project = projectSupplier.get(); if (project == null) { return Collections.emptyList(); } else { @@ -121,18 +111,15 @@ public class MavenProjectClasspath extends JandexClasspath { } public Path getOutputFolder() { - MavenProject project = projectSupplier.get(); return project == null ? null : new File(project.getBuild().getOutputDirectory()).toPath(); } private Optional getArtifactFromJarFile(File file) throws MavenException { - MavenProject project = projectSupplier.get(); return project.getArtifacts().stream().filter(a -> file.equals(a.getFile())).findFirst(); } @Override public ImmutableList getClasspathResources() { - MavenProject project = projectSupplier.get(); if (project == null) { return ImmutableList.of(); } @@ -188,7 +175,6 @@ public class MavenProjectClasspath extends JandexClasspath { // } protected IJavadocProvider createParserJavadocProvider(File classpathResource) { - MavenProject project = projectSupplier.get(); if (project == null) { return null; } @@ -225,7 +211,6 @@ public class MavenProjectClasspath extends JandexClasspath { } protected IJavadocProvider createHtmlJavdocProvider(File classpathResource) { - MavenProject project = projectSupplier.get(); if (project == null) { return null; } @@ -267,7 +252,7 @@ public class MavenProjectClasspath extends JandexClasspath { MavenProjectClasspath other = (MavenProjectClasspath) obj; try { if (pom.equals(other.pom) - && projectSupplier.get().equals(other.projectSupplier.get())) { + && Objects.equal(project, other.project)) { return super.equals(obj); } } catch (Throwable t) { diff --git a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/classpathfile/JavaProjectWithClasspathFileCache.java b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/classpathfile/JavaProjectWithClasspathFileCache.java index 6e459ffe9..3614c27b4 100644 --- a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/classpathfile/JavaProjectWithClasspathFileCache.java +++ b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/classpathfile/JavaProjectWithClasspathFileCache.java @@ -12,13 +12,13 @@ package org.springframework.ide.vscode.commons.maven.java.classpathfile; import java.io.File; +import org.springframework.ide.vscode.commons.languageserver.Sts4LanguageServer; import org.springframework.ide.vscode.commons.languageserver.java.AbstractFileToProjectCache; -import org.springframework.ide.vscode.commons.util.FileObserver; public class JavaProjectWithClasspathFileCache extends AbstractFileToProjectCache { - public JavaProjectWithClasspathFileCache(FileObserver fileObserver) { - super(fileObserver, false, null); + public JavaProjectWithClasspathFileCache(Sts4LanguageServer server) { + super(server, false, null); } @Override diff --git a/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/MavenProjectCacheTest.java b/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/MavenProjectCacheTest.java new file mode 100644 index 000000000..646226841 --- /dev/null +++ b/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/MavenProjectCacheTest.java @@ -0,0 +1,256 @@ +/******************************************************************************* + * Copyright (c) 2017 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.commons.maven; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.FileVisitOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.ide.vscode.commons.java.DelegatingCachedClasspath; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.DiagnosticService; +import org.springframework.ide.vscode.commons.languageserver.ProgressService; +import org.springframework.ide.vscode.commons.languageserver.Sts4LanguageServer; +import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver.Listener; +import org.springframework.ide.vscode.commons.languageserver.util.ShowMessageException; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleWorkspaceService; +import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; +import org.springframework.ide.vscode.commons.maven.java.MavenProjectCache; +import org.springframework.ide.vscode.commons.util.BasicFileObserver; + +import com.google.common.collect.ImmutableList; + +/** + * Tests for {@link MavenProjectCache} + * + * @author Alex Boyko + * + */ +public class MavenProjectCacheTest { + + private Sts4LanguageServer server; + private BasicFileObserver fileObserver; + private Path testProjectPath; + private File pomFile; + private String pomFileContents; + + @Before + public void setup() throws Exception { + fileObserver = new BasicFileObserver(); + server = mock(Sts4LanguageServer.class); + SimpleWorkspaceService workspaceService = mock(SimpleWorkspaceService.class); + when(workspaceService.getFileObserver()).thenReturn(fileObserver); + when(server.getWorkspaceService()).thenReturn(workspaceService); + + testProjectPath = Paths + .get(DependencyTreeTest.class.getResource("/empty-boot-project-with-classpath-file").toURI()); + pomFile = testProjectPath.resolve(MavenCore.POM_XML).toFile(); + pomFileContents = new String(Files.readAllBytes(pomFile.toPath()), Charset.defaultCharset()); + + Path cacheFolerPath = testProjectPath.resolve(IJavaProject.PROJECT_CACHE_FOLDER); + if (cacheFolerPath.toFile().exists()) { + Files.walk(cacheFolerPath, FileVisitOption.FOLLOW_LINKS).map(Path::toFile).forEach(File::delete); + Files.delete(cacheFolerPath); + } + } + + @After + public void tearDown() throws Exception { + // restore original content + writeContent(pomFile, pomFileContents); + } + + private static void writeContent(File file, String content) throws IOException { + FileWriter writer = null; + try { + writer = new FileWriter(file); + writer.write(content); + } finally { + writer.close(); + } + } + + @Test + public void testPomFileChanges() throws Exception { + MavenProjectCache cache = new MavenProjectCache(server, MavenCore.getDefault(), false, null); + IJavaProject[] projectChanged = new IJavaProject[] { null }; + IJavaProject[] projectDeleted = new IJavaProject[] { null }; + cache.addListener(new Listener() { + @Override + public void created(IJavaProject project) { + } + + @Override + public void changed(IJavaProject project) { + projectChanged[0] = project; + } + + @Override + public void deleted(IJavaProject project) { + projectDeleted[0] = project; + } + }); + + // Get the project from cache + MavenJavaProject cachedProject = cache.project(pomFile); + assertNotNull(cachedProject); + + ImmutableList calculatedClassPath = cachedProject.getClasspath().getClasspathEntries(); + assertEquals(48, calculatedClassPath.size()); + + fileObserver.notifyFileChanged(pomFile.toURI().toString()); + assertNull(projectChanged[0]); + + writeContent(pomFile, + new String(Files.readAllBytes(testProjectPath.resolve("pom.newxml")), Charset.defaultCharset())); + fileObserver.notifyFileChanged(pomFile.toURI().toString()); + assertNotNull(projectChanged[0]); + assertEquals(cachedProject, projectChanged[0]); + calculatedClassPath = cachedProject.getClasspath().getClasspathEntries(); + assertEquals(49, calculatedClassPath.size()); + + fileObserver.notifyFileDeleted(pomFile.toURI().toString()); + assertEquals(cachedProject, projectDeleted[0]); + } + + @Test + public void testClasspathCaching() throws Exception { + Path testProjectPath = Paths + .get(DependencyTreeTest.class.getResource("/empty-boot-project-with-classpath-file").toURI()); + + Path cacheFolder = testProjectPath.resolve(IJavaProject.PROJECT_CACHE_FOLDER); + + final File classpathCacheFile = cacheFolder.resolve(DelegatingCachedClasspath.CLASSPATH_DATA_CACHE_FILE) + .toFile(); + + AtomicBoolean progressDone = new AtomicBoolean(); + + ProgressService progressService = mock(ProgressService.class); + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + progressDone.set(true); + return null; + } + }).when(progressService).progressEvent(any(String.class), (String) isNull()); + + when(server.getProgressService()).thenReturn(progressService); + + assertFalse(classpathCacheFile.exists()); + + MavenProjectCache cache = new MavenProjectCache(server, MavenCore.getDefault(), true, cacheFolder); + MavenJavaProject project = cache.project(pomFile); + assertTrue(project.getClasspath().getClasspathEntries().isEmpty()); + + CompletableFuture.runAsync(() -> { + while (!progressDone.get()) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }).get(10, TimeUnit.SECONDS); + + assertTrue(classpathCacheFile.exists()); + assertEquals(48, project.getClasspath().getClasspathEntries().size()); + + progressDone.set(false); + + // Reset the cache + cache = new MavenProjectCache(server, MavenCore.getDefault(), true, cacheFolder); + + // Check loaded from cache file + project = cache.project(pomFile); + assertEquals(48, project.getClasspath().getClasspathEntries().size()); + } + + @Test + public void testErrorLoadingProject() throws Exception { + Path testProjectPath = Paths + .get(DependencyTreeTest.class.getResource("/empty-boot-project-with-classpath-file").toURI()); + + Path cacheFolder = testProjectPath.resolve(IJavaProject.PROJECT_CACHE_FOLDER); + + AtomicBoolean progressDone = new AtomicBoolean(); + + ProgressService progressService = mock(ProgressService.class); + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + progressDone.set(true); + return null; + } + }).when(progressService).progressEvent(any(String.class), (String) isNull()); + + when(server.getProgressService()).thenReturn(progressService); + + DiagnosticService diagnosticService = mock(DiagnosticService.class); + when(server.getDiagnosticService()).thenReturn(diagnosticService); + + MavenProjectCache cache = new MavenProjectCache(server, MavenCore.getDefault(), true, cacheFolder); + MavenJavaProject project = cache.project(pomFile); + assertTrue(project.getClasspath().getClasspathEntries().isEmpty()); + + CompletableFuture.runAsync(() -> { + while (!progressDone.get()) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }).get(10, TimeUnit.SECONDS); + progressDone.set(false); + verify(diagnosticService, never()).diagnosticEvent(any(ShowMessageException.class)); + + writeContent(pomFile, ""); + fileObserver.notifyFileChanged(pomFile.toURI().toString()); + CompletableFuture.runAsync(() -> { + while (!progressDone.get()) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }).get(10, TimeUnit.SECONDS); + progressDone.set(false); + verify(diagnosticService, times(1)).diagnosticEvent(any(ShowMessageException.class)); + } +} diff --git a/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/MavenProjectManagerTest.java b/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/MavenProjectManagerTest.java deleted file mode 100644 index 4a419fe19..000000000 --- a/headless-services/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/MavenProjectManagerTest.java +++ /dev/null @@ -1,104 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017 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.commons.maven; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.assertj.core.util.Files; -import org.junit.Test; -import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver.Listener; -import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; -import org.springframework.ide.vscode.commons.maven.java.MavenProjectCache; -import org.springframework.ide.vscode.commons.util.BasicFileObserver; - -import com.google.common.collect.ImmutableList; - -/** - * Tests for {@link MavenProjectCache} - * - * @author Alex Boyko - * - */ -public class MavenProjectManagerTest { - - private static void writeContent(File file, String content) throws IOException { - FileWriter writer = null; - try { - writer = new FileWriter(file); - writer.write(content); - } finally { - writer.close(); - } - } - - @Test - public void testPomFileChanges() throws Exception { - Path testProjectPath = Paths.get(DependencyTreeTest.class.getResource("/empty-boot-project-with-classpath-file").toURI()); - File pomFile = testProjectPath.resolve(MavenCore.POM_XML).toFile(); - - String pomFileContents = Files.contentOf(pomFile, Charset.defaultCharset()); - - try { - BasicFileObserver fileObserver = new BasicFileObserver(); - MavenProjectCache cache = new MavenProjectCache(fileObserver, MavenCore.getDefault(), false, null); - IJavaProject[] projectChanged = new IJavaProject[] { null }; - IJavaProject[] projectDeleted = new IJavaProject[] { null }; - cache.addListener(new Listener() { - @Override - public void created(IJavaProject project) {} - - @Override - public void changed(IJavaProject project) { - projectChanged[0] = project; - } - @Override - public void deleted(IJavaProject project) { - projectDeleted[0] = project; - } - }); - - // Get the project from cache - MavenJavaProject cachedProject = cache.project(pomFile); - assertNotNull(cachedProject); - - ImmutableList calculatedClassPath = cachedProject.getClasspath().getClasspathEntries(); - assertEquals(48, calculatedClassPath.size()); - - fileObserver.notifyFileChanged(pomFile.toURI().toString()); - assertNull(projectChanged[0]); - - writeContent(pomFile, Files.contentOf(testProjectPath.resolve("pom.newxml").toFile(), Charset.defaultCharset())); - fileObserver.notifyFileChanged(pomFile.toURI().toString()); - assertNotNull(projectChanged[0]); - assertEquals(cachedProject, projectChanged[0]); - calculatedClassPath = cachedProject.getClasspath().getClasspathEntries(); - assertEquals(49, calculatedClassPath.size()); - - fileObserver.notifyFileDeleted(pomFile.toURI().toString()); - assertEquals(cachedProject, projectDeleted[0]); - } finally { - //restore original content - writeContent(pomFile, pomFileContents); - } - } - - -}