Fix bug in UriUtil.normalize and add some test cases for it.
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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) && (
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user