From 27551f6762ff90eaa4120a03881b1471ca7d0b20 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Thu, 10 May 2018 17:20:17 -0700 Subject: [PATCH] Dealing with project deleted on file system by user. --- .../test/ClasspathListenerHandlerTest.java | 90 ++++++++++++++----- .../ReusableClasspathListenerHandler.java | 23 ++++- .../yaml-support/yaml.tmLanguage | 16 ++-- 3 files changed, 96 insertions(+), 33 deletions(-) diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/src/org/springframework/tooling/jdt/ls/commons/test/ClasspathListenerHandlerTest.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/src/org/springframework/tooling/jdt/ls/commons/test/ClasspathListenerHandlerTest.java index 5910ec0b3..64ff86550 100644 --- a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/src/org/springframework/tooling/jdt/ls/commons/test/ClasspathListenerHandlerTest.java +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/src/org/springframework/tooling/jdt/ls/commons/test/ClasspathListenerHandlerTest.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.springframework.tooling.jdt.ls.commons.test; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -21,11 +22,11 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.resources.WorkspaceJob; import org.eclipse.core.runtime.CoreException; @@ -38,26 +39,29 @@ import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.core.JavaCore; import org.junit.After; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.springframework.tooling.jdt.ls.commons.Logger; import org.springframework.tooling.jdt.ls.commons.classpath.Classpath; import org.springframework.tooling.jdt.ls.commons.classpath.Classpath.CPE; import org.springframework.tooling.jdt.ls.commons.classpath.ClientCommandExecutor; import org.springframework.tooling.jdt.ls.commons.classpath.ReusableClasspathListenerHandler; import org.springsource.ide.eclipse.commons.frameworks.test.util.ACondition; +import org.springsource.ide.eclipse.commons.frameworks.test.util.Asserter; import junit.framework.AssertionFailedError; public class ClasspathListenerHandlerTest { - + private MockClasspathCache classpaths = new MockClasspathCache(); private ReusableClasspathListenerHandler service = new ReusableClasspathListenerHandler(classpaths); - + @Test public void classpathIsSentForExistingProject() throws Exception { String projectName = "classpath-test-simple-java-project"; IProject project = createTestProject(projectName); File loc = project.getLocation().toFile(); - + service.addClasspathListener(classpaths.commandId); ACondition.waitFor("Project with classpath to appear", Duration.ofSeconds(50), () -> { Classpath cp = classpaths.getFor(loc).classpath; @@ -69,7 +73,7 @@ public class ClasspathListenerHandlerTest { @Test public void classpathIsSentForNewProject_and_removedForDeletedProject() throws Exception { service.addClasspathListener(classpaths.commandId); - + String projectName = "classpath-test-simple-java-project"; IProject project = createTestProject(projectName); File loc = project.getLocation().toFile(); @@ -84,15 +88,37 @@ public class ClasspathListenerHandlerTest { Info cp = classpaths.getFor(loc); assertNull(cp); }); - + } + + @Test public void classpathIsRemovedWhenProjectDeletedFromFileSystem() throws Exception { + String projectName = "classpath-test-simple-java-project"; + IProject project = createTestProject(projectName); + File loc = project.getLocation().toFile(); + + service.addClasspathListener(classpaths.commandId); + ACondition.waitFor("Project with classpath to appear", Duration.ofSeconds(50), () -> { + Classpath cp = classpaths.getFor(loc).classpath; + assertTrue(cp.getEntries().stream().filter(cpe -> Classpath.isSource(cpe)).count()==1); //has 1 source entry + assertClasspath(cp, cp.getEntries().stream().filter(cpe -> Classpath.isBinary(cpe) && cpe.isSystem()).count()>=1); //has some system libraries + }); + + FileUtils.deleteQuietly(loc); + safe(() -> project.refreshLocal(IResource.DEPTH_INFINITE, null)); + + ACondition.waitFor("Project to disapear", Duration.ofSeconds(50), () -> { + Info cp = classpaths.getFor(loc); + assertNull(cp); + }); } ///////////// harness stuff below /////////////////////////////////////////////// - + + @Rule public TemporaryFolder tmp = new TemporaryFolder(); + static class Info { public final String name; public final Classpath classpath; - + private Info(String name, Classpath cp) { super(); this.name = name; @@ -103,9 +129,9 @@ public class ClasspathListenerHandlerTest { public class MockClasspathCache implements ClientCommandExecutor { String commandId = RandomStringUtils.randomAlphabetic(8); - + Map classpaths = new HashMap<>(); - + @Override public synchronized Object executeClientCommand(String id, Object... params) throws Exception { if (id.equals(commandId)) { @@ -131,17 +157,17 @@ public class ClasspathListenerHandlerTest { public void dispose() throws Exception { service.removeClasspathListener(commandId); - deleteAllProjects(); } } - + @After public void tearDown() throws Exception { classpaths.dispose(); + deleteAllProjects(); assertTrue(service.hasNoActiveSubscriptions()); } - + private static void assertClasspath(Classpath cp, boolean b) { if (!b) { StringBuilder buf = new StringBuilder(); @@ -154,40 +180,56 @@ public class ClasspathListenerHandlerTest { } private IProject createTestProject(String name) throws Exception { - File testProjectLocation = new File(FileLocator.toFileURL(Platform.getBundle("org.springframework.tooling.jdt.ls.commons.test").getEntry("test-projects/"+name)).toURI()); + File testProjectSourceLocation = new File(FileLocator.toFileURL(Platform.getBundle("org.springframework.tooling.jdt.ls.commons.test").getEntry("test-projects/"+name)).toURI()); + + File testProjectLocation = tmp.newFolder(name); + FileUtils.copyDirectory(testProjectSourceLocation, testProjectLocation); + IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(name); IProjectDescription desc = ResourcesPlugin.getWorkspace().newProjectDescription(null); desc.setName(name); desc.setLocation(Path.fromOSString(testProjectLocation.toString())); project.create(desc, null); project.open(null); - + assertTrue(project.hasNature(JavaCore.NATURE_ID)); return project; - } - + } + public static void deleteAllProjects() throws Exception { CompletableFuture done = new CompletableFuture(); WorkspaceJob job = new WorkspaceJob("Delete projects") { @Override public IStatus runInWorkspace(IProgressMonitor arg0) throws CoreException { try { - ResourcesPlugin.getWorkspace().getRuleFactory().buildRule(); - IProject[] allProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); - for (IProject project : allProjects) { - project.refreshLocal(IResource.DEPTH_INFINITE, null); - project.close(null); - project.delete(false, true, new NullProgressMonitor()); - } + ACondition.waitFor("Deleting all projects", Duration.ofMinutes(1), () -> { + ResourcesPlugin.getWorkspace().getRuleFactory().buildRule(); + IProject[] allProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); + for (IProject project : allProjects) { + project.refreshLocal(IResource.DEPTH_INFINITE, null); + safe(() -> project.close(null)); + project.delete(false, true, new NullProgressMonitor()); + assertFalse(project.exists()); + } + }); done.complete(null); } catch (Throwable e) { done.completeExceptionally(e); } return Status.OK_STATUS; } + }; job.setRule(ResourcesPlugin.getWorkspace().getRuleFactory().buildRule()); job.schedule(); done.get(); } + private static void safe(Asserter doit) { + try { + doit.execute(); + } catch (Throwable e) { + System.err.println("Ignore exception: "+e.getMessage()); + } + } + } 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 f9d7a906a..c7f2f2454 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 @@ -17,6 +17,7 @@ import java.net.URI; import java.util.HashMap; import java.util.Map; +import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; @@ -54,6 +55,26 @@ public class ReusableClasspathListenerHandler { } } + private boolean projectExists(IJavaProject jp) { + //We can't really deal with projects that don't exist in disk. So using this more strict 'exists' check + //makes sure anything that looks like it doesn't exist on disk is treated as if it simply doesn't exist + //at all. This kind of addresses a issue caused by Eclipse's idiotic behavior when it comes to deleting + //a project's files from the file system... Eclipse recreates a 'vanilla' project in the workspace, + //simply refusing to accept the fact that the project is actually gone. + if (jp.exists()) { + try { + URI loc = getProjectLocation(jp); + if (loc!=null) { + File f = new File(loc); + return f.isDirectory(); + } + } catch (Exception e) { + //Something bogus about this project... so just pretend it doesn't exist. + } + } + return false; + } + class Subscribptions { @@ -86,7 +107,7 @@ public class ReusableClasspathListenerHandler { if (projectLoc==null) { Logger.log("Could not send event for project because no project location: "+jp.getElementName()); } else { - boolean exsits = jp.exists(); + boolean exsits = projectExists(jp); boolean open = true; // WARNING: calling is jp.isOpen is unreliable and subject to race condition. After a POST_CHAGE project open event // this should be true but it typically is not unless you wait for some time. No idea how you would know // how long you should wait (200ms is not enough, and that seems pretty long). Isn't it kind of the point diff --git a/vscode-extensions/vscode-spring-boot/yaml-support/yaml.tmLanguage b/vscode-extensions/vscode-spring-boot/yaml-support/yaml.tmLanguage index efc4c8ab0..4a1b7b15b 100644 --- a/vscode-extensions/vscode-spring-boot/yaml-support/yaml.tmLanguage +++ b/vscode-extensions/vscode-spring-boot/yaml-support/yaml.tmLanguage @@ -220,12 +220,12 @@ 1 name - punctuation.definition.block.scalar.literal.yaml + keyword.control.flow.block-scalar.literal.yaml 2 name - punctuation.definition.block.scalar.folded.yaml + keyword.control.flow.block-scalar.folded.yaml 3 @@ -235,7 +235,7 @@ 4 name - support.other.chomping-indicator.yaml + storage.modifier.chomping-indicator.yaml 5 @@ -271,7 +271,7 @@ block-sequence match - (-)( |\t|$) + (-)(?!\S) name punctuation.definition.block.sequence.item.yaml @@ -370,8 +370,8 @@ (?:[ \t]+ ((?:!(?:[0-9A-Za-z\-]*!)?)) (?:[ \t]+ ( - ! (?x: %\p{XDigit}{2} | [0-9A-Za-z\-#;/?:@&=+$,_.!~*'()\[\]] )* - | (?![,!\[\]{}]) (?x: %\p{XDigit}{2} | [0-9A-Za-z\-#;/?:@&=+$,_.!~*'()\[\]] )+ + ! (?x: %[0-9A-Fa-f]{2} | [0-9A-Za-z\-#;/?:@&=+$,_.!~*'()\[\]] )* + | (?![,!\[\]{}]) (?x: %[0-9A-Fa-f]{2} | [0-9A-Za-z\-#;/?:@&=+$,_.!~*'()\[\]] )+ ) )? )? @@ -1124,8 +1124,8 @@ (?x) \G (?: - ! < (?: %\p{XDigit}{2} | [0-9A-Za-z\-#;/?:@&=+$,_.!~*'()\[\]] )+ > - | (?:!(?:[0-9A-Za-z\-]*!)?) (?: %\p{XDigit}{2} | [0-9A-Za-z\-#;/?:@&=+$_.~*'()] )+ + ! < (?: %[0-9A-Fa-f]{2} | [0-9A-Za-z\-#;/?:@&=+$,_.!~*'()\[\]] )+ > + | (?:!(?:[0-9A-Za-z\-]*!)?) (?: %[0-9A-Fa-f]{2} | [0-9A-Za-z\-#;/?:@&=+$_.~*'()] )+ | ! ) (?=\ |\t|$)