Fix bug in UriUtil.normalize and add some test cases for it.

This commit is contained in:
Kris De Volder
2018-05-10 15:15:31 -07:00
parent bf7e0068e0
commit ecc2724822
8 changed files with 178 additions and 33 deletions

View File

@@ -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) {

View File

@@ -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 + "]";
}
}
}

View File

@@ -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<Object> 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";

View File

@@ -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) && (

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.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));
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);

View File

@@ -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) {
}
}