Make spring-boot-ls request batched classpath events ...

...and make it handle them properly, but also retain
support for non-batched events for backwards compatibility
with clients that don't support batched events.
This commit is contained in:
Kris De Volder
2019-05-28 12:11:51 -07:00
parent fb0ddc0469
commit 7c3cce32a9
2 changed files with 87 additions and 30 deletions

View File

@@ -29,6 +29,7 @@ import org.springframework.ide.vscode.commons.util.AsyncRunner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import reactor.core.Disposable;
@@ -57,19 +58,38 @@ public class ClasspathListenerManager {
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);
//Args are deserialized as com.google.gson.JsonElements.
if (((JsonElement) args.get(0)).isJsonArray()) {
// If events are batched... then they will arrive as a array of arrays.
for (Object arg : args) {
JsonArray event = (JsonArray) arg;
Classpath classpath = gson.fromJson((JsonElement)args.get(3), Classpath.class);
log.debug("classpath = {}", classpath);
String projectUri = event.get(0).getAsString();
log.debug("projectUri = {}", event);
String name = event.get(1).getAsString();
log.debug("name = {}", event);
boolean deleted = event.get(2).getAsBoolean();
log.debug("deleted = {}", deleted);
classpathListener.changed(new ClasspathListener.Event(projectUri, name, deleted, classpath));
Classpath classpath = gson.fromJson((JsonElement)event.get(3), Classpath.class);
log.debug("classpath = {}", classpath);
classpathListener.changed(new ClasspathListener.Event(projectUri, name, deleted, classpath));
}
} else {
//Still support non-batched events for backwards compatibility with clients
// that don't provide batched event support (e.g. IDEA client may only adopt this
// later, or not adopt it at all).
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";
}));
@@ -86,7 +106,7 @@ public class ClasspathListenerManager {
server.getClient().registerCapability(params)
));
Mono<Object> registerClasspathListener = Mono.defer(() -> Mono.fromFuture(
server.getClient().addClasspathListener(new ClasspathListenerParams(callbackCommandId))
server.getClient().addClasspathListener(new ClasspathListenerParams(callbackCommandId, true))
));
Disposable cleanups = () -> {

View File

@@ -12,7 +12,9 @@ package org.springframework.tooling.jdt.ls.commons.classpath;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -26,20 +28,19 @@ import org.eclipse.jdt.core.JavaCore;
import org.springframework.ide.vscode.commons.protocol.java.Classpath;
import org.springframework.tooling.jdt.ls.commons.Logger;
import com.google.common.collect.ImmutableList;
public class SendClasspathNotificationsJob extends Job {
private final ClientCommandExecutor conn;
private final Logger logger;
private String callbackCommandId;
private boolean isBatched;
public SendClasspathNotificationsJob(Logger logger, ClientCommandExecutor conn, String callbackId, boolean isBatched) {
super("Send Classpath Notifications");
this.logger = logger;
this.conn = conn;
this.callbackCommandId = callbackId;
this.isBatched = isBatched;
}
/**
* Used only if caller has requested 'batched' events. This buffer, accumulates messsages to be sent out all at once,
* rather than one by one.
*/
private List<Object> buffer;
/**
* To keep track of project locations. Without this we can't properly handle deletion events because
@@ -47,6 +48,18 @@ public class SendClasspathNotificationsJob extends Job {
* was deleted' event if we keep track of project locations ourselves.
*/
private Map<String, URI> projectLocations = new HashMap<>();
public final Queue<IJavaProject> queue = new ConcurrentLinkedQueue<>();
public SendClasspathNotificationsJob(Logger logger, ClientCommandExecutor conn, String callbackId, boolean isBatched) {
super("Send Classpath Notifications");
this.logger = logger;
this.conn = conn;
this.callbackCommandId = callbackId;
if (isBatched) {
buffer = new ArrayList<>();
}
}
private URI getProjectLocation(IJavaProject jp) {
URI loc = jp.getProject().getLocationURI();
@@ -60,7 +73,6 @@ public class SendClasspathNotificationsJob extends Job {
}
}
public final Queue<IJavaProject> queue = new ConcurrentLinkedQueue<>();
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
@@ -114,20 +126,45 @@ public class SendClasspathNotificationsJob extends Job {
logger.log(e);
}
}
try {
logger.log("executing callback "+callbackCommandId+" "+projectName+" "+deleted+" "+ classpath.getEntries().size());
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);
}
bufferMessage(projectLoc, deleted, projectName, classpath);
}
}
flush();
} catch (Exception e) {
logger.log(e);
}
return Status.OK_STATUS;
}
}
protected void bufferMessage(URI projectLoc, boolean deleted, String projectName, Classpath classpath) {
if (buffer!=null) {
logger.log("buffering callback "+callbackCommandId+" "+projectName+" "+deleted+" "+ classpath.getEntries().size());
buffer.add(ImmutableList.of(projectLoc.toString(), projectName, deleted, classpath));
} else {
try {
logger.log("executing callback "+callbackCommandId+" "+projectName+" "+deleted+" "+ classpath.getEntries().size());
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);
}
}
}
protected void flush() {
if (buffer!=null && !buffer.isEmpty()) {
try {
logger.log("executing callback "+callbackCommandId+" "+buffer.size()+" batched events");
Object r = conn.executeClientCommand(callbackCommandId, buffer.toArray(new Object[buffer.size()]));
logger.log("executing callback "+callbackCommandId+" SUCCESS ["+r+"]");
} catch (Exception e) {
logger.log("executing callback "+callbackCommandId+" FAILED");
logger.log(e);
} finally {
buffer.clear();
}
}
}
}