diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/ResourceListener.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/ResourceListener.java index b577284e2..aa124caf3 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/ResourceListener.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/ResourceListener.java @@ -11,6 +11,7 @@ package org.springframework.tooling.boot.ls; import java.net.URI; +import java.nio.file.Path; import java.nio.file.PathMatcher; import java.util.ArrayList; import java.util.List; @@ -20,6 +21,7 @@ import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; import org.eclipse.lsp4e.LSPEclipseUtils; import org.eclipse.lsp4j.DidChangeWatchedFilesParams; import org.eclipse.lsp4j.FileChangeType; @@ -79,7 +81,12 @@ public class ResourceListener implements IResourceChangeListener { } private boolean isApplicableFile(IFile resource) { - return pathMatchers.stream().filter(m -> m.matches(resource.getLocation().toFile().toPath())).findFirst().isPresent(); + IPath loc = resource.getLocation(); + if (loc!=null) { // Avoid NPE for resource that has no location (happens when project deleted) + Path locPath = resource.getLocation().toFile().toPath(); + return pathMatchers.stream().filter(m -> m.matches(locPath)).findFirst().isPresent(); + } + return false; } private static boolean isRelevantDelta(IResourceDelta delta) { diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/jdt/ls/ClasspathListener.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/jdt/ls/ClasspathListener.java index 9312f68d4..54ea62e31 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/jdt/ls/ClasspathListener.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/jdt/ls/ClasspathListener.java @@ -14,7 +14,7 @@ public interface ClasspathListener { void changed(Event event); - static class Event { + public static class Event { public final String projectUri; public final String name; @@ -29,6 +29,13 @@ public interface ClasspathListener { this.classpath = classpath; } + @Override + public String toString() { + return "Event [projectUri=" + projectUri + ", name=" + name + ", deleted=" + deleted + ", classpath=" + + classpath + "]"; + } } + + } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/jdt/ls/ClasspathListenerManager.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/jdt/ls/ClasspathListenerManager.java index c60ba0126..2575513b9 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/jdt/ls/ClasspathListenerManager.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/jdt/ls/ClasspathListenerManager.java @@ -10,11 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.languageserver.jdt.ls; -import static org.springframework.ide.vscode.commons.languageserver.util.AsyncRunner.thenLog; - import java.util.List; import java.util.UUID; -import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.lsp4j.ExecuteCommandParams; @@ -54,14 +51,20 @@ public class ClasspathListenerManager { // 1. register callback command handler in SimpleLanguageServer Disposable unregisterCommand = server.onCommand(callbackCommandId, (ExecuteCommandParams callbackParams) -> async.invoke(() -> { + log.debug("callback {} received {}", callbackCommandId, callbackParams); List args = callbackParams.getArguments(); + log.debug("args = {}", args); //Note: not sure... but args might be deserialized as com.google.gson.JsonElement's. //If so the code below is not correct (casts will fail). String projectUri = ((JsonElement) args.get(0)).getAsString(); + log.debug("projectUri = {}", args); String name = ((JsonElement) args.get(1)).getAsString(); + log.debug("name = {}", args); boolean deleted = ((JsonElement)args.get(2)).getAsBoolean(); + log.debug("deleted = {}", deleted); Classpath classpath = gson.fromJson((JsonElement)args.get(3), Classpath.class); + log.debug("classpath = {}", classpath); classpathListener.changed(new ClasspathListener.Event(projectUri, name, deleted, classpath)); return "done"; diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/UriUtil.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/UriUtil.java index e840b3b10..660a0416e 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/UriUtil.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/UriUtil.java @@ -29,7 +29,17 @@ public class UriUtil { try { if (uriVal != null && uriVal.startsWith("file:")) { File file = new File(URI.create(uriVal)); - return file.toURI().toString(); + uriVal = file.toURI().toString(); + //Careful!!! If the project uri points to a existing project... then it will be + //a directory and then the uri we computed will get a slash at the end. + //If, on the other hand, it doesn't exist because it got deleted. Then it will not get a slash + //(because something that doesn't exist isn't considered to be a directory). + //We really don't want the uri to change after it got deleted. + //So... make sure we never have the slash: + while (uriVal.endsWith("/")) { + uriVal = uriVal.substring(0, uriVal.length()-1); + } + return uriVal; } } catch (Exception e) { @@ -37,6 +47,10 @@ public class UriUtil { return uriVal; } + /** + * Caution, the implementation assumes that it is only called with + * normalized uris! + */ public static boolean contains(String projectUri, String uri) { if (projectUri.length() < uri.length()) { return uri.startsWith(projectUri) && ( diff --git a/headless-services/commons/commons-util/src/test/java/org/springframework/ide/vscode/commons/util/UriUtilTest.java b/headless-services/commons/commons-util/src/test/java/org/springframework/ide/vscode/commons/util/UriUtilTest.java new file mode 100644 index 000000000..efa6f37c4 --- /dev/null +++ b/headless-services/commons/commons-util/src/test/java/org/springframework/ide/vscode/commons/util/UriUtilTest.java @@ -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.commons.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class UriUtilTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void normalize_deleted_folder_uri() throws Exception { + File folder = temp.newFolder(); + assertTrue(folder.exists()); + + String folderUri = folder.toURI().toString(); + assertTrue(folderUri.endsWith("/")); + folderUri = UriUtil.normalize(folderUri); + + folder.delete(); + assertFalse(folder.exists()); + String deletedFolderUri = folder.toURI().toString(); + assertFalse(deletedFolderUri.endsWith("/")); + deletedFolderUri = UriUtil.normalize(deletedFolderUri); + + assertEquals(folderUri, deletedFolderUri); + } + + @Test + public void normalize_slashes() throws Exception { + String[] cases = { + "file:/foo/bar", + "file:/foo/bar/", + "file:///foo/bar/", + "file:///foo/bar", + }; + String previous = null; + for (String uri : cases) { + String normalized = UriUtil.normalize(uri); + if (previous!=null) { + assertEquals(previous, normalized); + } + previous = normalized; + } + } + + @Test + public void contains() throws Exception { + String[] roots = { + "file:/foo/bar", + "file:/foo/bar/", + "file:///foo/bar/", + "file:///foo/bar", + }; + String[] children = { + "file:/foo/bar/child", + "file:/foo/bar/child/baby", + "file:///foo/bar/foo.txt", + "file:///foo/bar/", + "file:///foo/bar" + }; + for (String root : roots) { + for (String child : children) { + //Note: implementation of 'contains' assumes normalized input so... + root = UriUtil.normalize(root); + child = UriUtil.normalize(child); + + assertTrue(UriUtil.contains(root, child)); + } + } + + String[] not_children = { + "file:/foo", + "file:/foo/", + "file:///foo", + "file:///foo/", + "file:/foo/whatever", + "file:/foo/whatever/", + "file:///foo/whatever", + "file:///foo/whatever/", + "file:///foo/barack", //starts with '/foo/bar' but is not a child! + "file:///foo/barack/", //starts with '/foo/bar' but is not a child! + "file:/foo/barack", //starts with '/foo/bar' but is not a child! + "file:/foo/barack/" //starts with '/foo/bar' but is not a child! + }; + for (String root : roots) { + for (String child : not_children) { + //Note: implementation of 'contains' assumes normalized input so... + root = UriUtil.normalize(root); + child = UriUtil.normalize(child); + + assertFalse(UriUtil.contains(root, child)); + } + } + } + +} diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/Logger.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/Logger.java index e2a2bdc9d..f856a2992 100644 --- a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/Logger.java +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/Logger.java @@ -21,17 +21,23 @@ import java.util.Date; */ public class Logger { + private static boolean USE_SYS_ERR = false; + private static PrintWriter printwriter; static { - File file = new File(System.getProperty("java.io.tmpdir")); - file = new File(file, "stsjdt.log"); - try { - printwriter = new PrintWriter(new FileOutputStream(file), true); - log("======== "+new Date()+" ======="); - } catch (FileNotFoundException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + if (USE_SYS_ERR) { + printwriter = new PrintWriter(System.err); + } else { + File file = new File(System.getProperty("java.io.tmpdir")); + file = new File(file, "stsjdt.log"); + try { + printwriter = new PrintWriter(new FileOutputStream(file), true); + log("======== "+new Date()+" ======="); + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } } diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ReusableClasspathListenerHandler.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ReusableClasspathListenerHandler.java index 6f180baef..f9d7a906a 100644 --- a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ReusableClasspathListenerHandler.java +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ReusableClasspathListenerHandler.java @@ -110,8 +110,8 @@ public class ReusableClasspathListenerHandler { } try { Logger.log("executing callback "+callbackCommandId+" "+projectName+" "+deleted+" "+(classpath==null ? "" : classpath.getEntries().size())); - conn.executeClientCommand(callbackCommandId, projectLoc.toString(), projectName, deleted, classpath); - Logger.log("executing callback "+callbackCommandId+" SUCCESS"); + Object r = conn.executeClientCommand(callbackCommandId, projectLoc.toString(), projectName, deleted, classpath); + Logger.log("executing callback "+callbackCommandId+" SUCCESS ["+r+"]"); } catch (Exception e) { Logger.log("executing callback "+callbackCommandId+" FAILED"); Logger.log(e); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java index cf37ca8a0..bead461d0 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java @@ -10,44 +10,30 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.jdt.ls; -import java.io.File; import java.net.URI; -import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ide.vscode.commons.jandex.JandexClasspath; 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.java.JavaProject; -import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath; -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.CollectorUtil; 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 com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; import reactor.core.Disposable; @@ -72,16 +58,24 @@ public class JdtLsProjectCache implements JavaProjectsService { disposable.complete(server.addClasspathListener(new ClasspathListener() { @Override public void changed(Event event) { + log.info("claspath event received {}", event); initialized.thenRun(() -> { + log.info("initialized.thenRun block entered"); try { synchronized (table) { String uri = UriUtil.normalize(event.projectUri); + log.info("uri = {}", uri); if (event.deleted) { + log.info("event.deleted = true"); JavaProject deleted = table.remove(uri); if (deleted!=null) { + log.info("removed from table = true"); notifyDelete(deleted); + } else { + log.warn("Deleted project not removed because uri {} not found in {}", uri, table.keySet()); } } else { + log.info("deleted = false"); JavaProject newProject = new JavaProject(getFileObserver(), new URI(uri), new ClasspathData(event.name, event.classpath.getEntries())); JavaProject oldProject = table.put(uri, newProject); if (oldProject != null) { @@ -183,10 +177,10 @@ public class JdtLsProjectCache implements JavaProjectsService { } } - private void logEvent(String type, JavaProject newProject) { + private void logEvent(String type, JavaProject project) { try { - log.info("Project "+type+": " + newProject.getLocationUri()); - log.info("Classpath has "+newProject.getClasspath().getClasspathEntries().size()+" entries"); + log.info("Project "+type+": " + project.getLocationUri()); + log.info("Classpath has "+project.getClasspath().getClasspathEntries().size()+" entries"); } catch (Exception e) { } }