diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/ClasspathListenerParams.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/ClasspathListenerParams.java new file mode 100644 index 000000000..36503d98a --- /dev/null +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/ClasspathListenerParams.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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.tooling.ls.eclipse.commons; + +public class ClasspathListenerParams { + + private String callbackCommandId; + + public ClasspathListenerParams(String callbackCommandId) { + super(); + this.callbackCommandId = callbackCommandId; + } + + public String getCallbackCommandId() { + return callbackCommandId; + } + + public void setCallbackCommandId(String callbackCommandId) { + this.callbackCommandId = callbackCommandId; + } + + @Override + public String toString() { + return "ClasspathListenerParams [callbackCommandId=" + callbackCommandId + "]"; + } + +} diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClient.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClient.java index 9e72b46fc..4b8428974 100644 --- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClient.java +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClient.java @@ -10,7 +10,10 @@ *******************************************************************************/ package org.springframework.tooling.ls.eclipse.commons; +import java.util.concurrent.CompletableFuture; + import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; +import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; import org.eclipse.lsp4j.services.LanguageClient; /** @@ -29,4 +32,10 @@ public interface STS4LanguageClient extends LanguageClient { // TODO: @JsonRequest("sts/moveCursor") // CompletableFuture moveCursor(CursorMovement cursorMovement); + @JsonRequest("sts/addClasspathListener") + CompletableFuture addClasspathListener(ClasspathListenerParams params); + + @JsonRequest("sts/removeClasspathListener") + CompletableFuture removeClasspathListener(ClasspathListenerParams classpathListenerParams); + } diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java index adf5a1ebe..f9ee43ae5 100644 --- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java @@ -16,6 +16,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; @@ -42,6 +43,7 @@ import org.eclipse.ui.texteditor.AbstractTextEditor; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.Futures; @SuppressWarnings("restriction") public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4LanguageClient { @@ -174,5 +176,17 @@ public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4La return null; } } + + @Override + public CompletableFuture addClasspathListener(ClasspathListenerParams params) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CompletableFuture removeClasspathListener(ClasspathListenerParams classpathListenerParams) { + // TODO Auto-generated method stub + return null; + } } diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/META-INF/MANIFEST.MF b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/META-INF/MANIFEST.MF index de71fc161..625386430 100644 --- a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/META-INF/MANIFEST.MF +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/META-INF/MANIFEST.MF @@ -5,8 +5,7 @@ Bundle-SymbolicName: org.springframework.tooling.jdt.ls.commons Bundle-Version: 1.0.0.qualifier Automatic-Module-Name: org.springframework.tooling.jdt.ls.commons Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Require-Bundle: org.eclipse.jdt.ls.core, - org.eclipse.core.runtime, +Require-Bundle: org.eclipse.core.runtime, org.eclipse.jdt.core, org.eclipse.core.resources Export-Package: org.springframework.tooling.jdt.ls.commons, diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ClientCommandExecutor.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ClientCommandExecutor.java new file mode 100644 index 000000000..84d795f4e --- /dev/null +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ClientCommandExecutor.java @@ -0,0 +1,5 @@ +package org.springframework.tooling.jdt.ls.commons.classpath; + +public interface ClientCommandExecutor { + Object executeClientCommand(String id, Object... params); +} 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 new file mode 100644 index 000000000..5d2895fb0 --- /dev/null +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ReusableClasspathListenerHandler.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * 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.tooling.jdt.ls.commons.classpath; + +import static org.springframework.tooling.jdt.ls.commons.Logger.log; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jdt.core.IJavaProject; +import org.springframework.tooling.jdt.ls.commons.Logger; +import org.springframework.tooling.jdt.ls.commons.classpath.ClasspathListenerManager.ClasspathListener; + +/** + * {@link ReusableClasspathListenerHandler} is an 'abstracted' version of the jdtls ClasspathListenerHandler. + */ +public class ReusableClasspathListenerHandler { + + private ClientCommandExecutor conn; + + public ReusableClasspathListenerHandler(ClientCommandExecutor conn) { + this.conn = conn; + log("Instantiating ReusableClasspathListenerHandler"); + } + + class Subscribptions { + + private Map subscribers = null; + + public synchronized void subscribe(String callbackCommandId) { + Logger.log("subscribing to classpath changes: " + callbackCommandId); + if (subscribers==null) { + subscribers = new HashMap<>(1); + } + subscribers.computeIfAbsent(callbackCommandId, (cid) -> new ClasspathListenerManager(new ClasspathListener() { + @Override + public void classpathChanged(IJavaProject jp) { + sendNotification(callbackCommandId, jp); + } + }, true)); + Logger.log("subsribers = " + subscribers.keySet()); + } + + private void sendNotification(String callbackCommandId, IJavaProject jp) { + //TODO: make one Job to accumulate all requested notification and work more efficiently by batching + // and avoiding multiple executions of duplicated requests. + new Job("SendClasspath notification") { + @Override + protected IStatus run(IProgressMonitor monitor) { + log("Classpath changed " + jp.getElementName()); + String project = jp.getProject().getLocationURI().toString(); + boolean deleted = !jp.exists(); +// JavaClientConnection conn = JavaLanguageServerPlugin.getInstance().getClientConnection(); + String projectName = jp.getElementName(); + + Classpath classpath = null; + if (!deleted) { + try { + classpath = ClasspathUtil.resolve(jp); + } catch (Exception e) { + Logger.log(e); + } + } + try { + Logger.log("executing callback "+callbackCommandId+" "+projectName+" "+deleted+" "+(classpath==null ? "" : classpath.getEntries().size())); + conn.executeClientCommand(callbackCommandId, project, projectName, deleted, classpath); + Logger.log("executing callback "+callbackCommandId+" SUCCESS"); + } catch (Exception e) { + Logger.log("executing callback "+callbackCommandId+" FAILED"); + Logger.log(e); + } + return Status.OK_STATUS; + } + } + .schedule(); + } + + public synchronized void unsubscribe(String callbackCommandId) { + Logger.log("unsubscribing from classpath changes: " + callbackCommandId); + if (subscribers != null) { + ClasspathListenerManager mgr = subscribers.remove(callbackCommandId); + if (mgr!=null) { + mgr.dispose(); + } + if (subscribers.isEmpty()) { + subscribers = null; + } + } + Logger.log("subsribers = " + subscribers.keySet()); + } + } + + private Subscribptions subscribptions = new Subscribptions(); + + public Object removeClasspathListener(String callbackCommandId) { + log("ClasspathListenerHandler addClasspathListener " + callbackCommandId); + subscribptions.unsubscribe(callbackCommandId); + log("ClasspathListenerHandler addClasspathListener " + callbackCommandId + " => OK"); + return "ok"; + } + + public Object addClasspathListener(String callbackCommandId) { + log("ClasspathListenerHandler addClasspathListener " + callbackCommandId); + subscribptions.subscribe(callbackCommandId); + log("ClasspathListenerHandler addClasspathListener " + callbackCommandId + " => OK"); + return "ok"; + } + +} diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/ClasspathListenerHandler.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/ClasspathListenerHandler.java index 9ebd3187c..eb43b3212 100644 --- a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/ClasspathListenerHandler.java +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/ClasspathListenerHandler.java @@ -12,110 +12,38 @@ package org.springframework.tooling.jdt.ls.extension; import static org.springframework.tooling.jdt.ls.commons.Logger.log; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.jobs.Job; -import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.ls.core.internal.IDelegateCommandHandler; import org.eclipse.jdt.ls.core.internal.JavaClientConnection; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; 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.ClasspathListenerManager; -import org.springframework.tooling.jdt.ls.commons.classpath.ClasspathListenerManager.ClasspathListener; -import org.springframework.tooling.jdt.ls.commons.classpath.ClasspathUtil; +import org.springframework.tooling.jdt.ls.commons.classpath.ClientCommandExecutor; +import org.springframework.tooling.jdt.ls.commons.classpath.ReusableClasspathListenerHandler; @SuppressWarnings("restriction") public class ClasspathListenerHandler implements IDelegateCommandHandler { - static final boolean isSupported = checkSupported(); - private static boolean checkSupported() { + private static ReusableClasspathListenerHandler handlerImpl = checkSupported(); + private static ReusableClasspathListenerHandler checkSupported() { try { JavaClientConnection.class.getMethod("executeClientCommand", String.class, Object[].class); - return true; + return new ReusableClasspathListenerHandler(new ClientCommandExecutor() { + @Override + public Object executeClientCommand(String id, Object... params) { + return JavaLanguageServerPlugin.getInstance().getClientConnection().executeClientCommand(id, params); + } + }); } catch (Exception e) { Logger.log(e); } - return false; + return null; } - static class Subscribptions { - - private static Map subscribers = null; - - public synchronized void subscribe(String callbackCommandId) { - Logger.log("subscribing to classpath changes: " + callbackCommandId); - if (subscribers==null) { - subscribers = new HashMap<>(1); - } - subscribers.computeIfAbsent(callbackCommandId, (cid) -> new ClasspathListenerManager(new ClasspathListener() { - @Override - public void classpathChanged(IJavaProject jp) { - sendNotification(callbackCommandId, jp); - } - }, true)); - Logger.log("subsribers = " + subscribers.keySet()); - } - - private void sendNotification(String callbackCommandId, IJavaProject jp) { - //TODO: make one Job to accumulate all requested notification and work more efficiently by batching - // and avoiding multiple executions of duplicated requests. - new Job("SendClasspath notification") { - @Override - protected IStatus run(IProgressMonitor monitor) { - log("Classpath changed " + jp.getElementName()); - String project = jp.getProject().getLocationURI().toString(); - boolean deleted = !jp.exists(); - JavaClientConnection conn = JavaLanguageServerPlugin.getInstance().getClientConnection(); - String projectName = jp.getElementName(); - - Classpath classpath = null; - if (!deleted) { - try { - classpath = ClasspathUtil.resolve(jp); - } catch (Exception e) { - Logger.log(e); - } - } - try { - Logger.log("executing callback "+callbackCommandId+" "+projectName+" "+deleted+" "+(classpath==null ? "" : classpath.getEntries().size())); - conn.executeClientCommand(callbackCommandId, project, projectName, deleted, classpath); - Logger.log("executing callback "+callbackCommandId+" SUCCESS"); - } catch (Exception e) { - Logger.log("executing callback "+callbackCommandId+" FAILED"); - Logger.log(e); - } - return Status.OK_STATUS; - } - } - .schedule(); - } - - public synchronized void unsubscribe(String callbackCommandId) { - Logger.log("unsubscribing from classpath changes: " + callbackCommandId); - if (subscribers != null) { - ClasspathListenerManager mgr = subscribers.remove(callbackCommandId); - if (mgr!=null) { - mgr.dispose(); - } - if (subscribers.isEmpty()) { - subscribers = null; - } - } - Logger.log("subsribers = " + subscribers.keySet()); - } - } - - private static Subscribptions subscribptions = new Subscribptions(); - @Override public Object executeCommand(String commandId, List arguments, IProgressMonitor monitor) throws Exception { - if (!isSupported) { + if (handlerImpl==null) { throw new UnsupportedOperationException("Command '"+commandId+"' not supported on older versions of JDT Language Server"); } log("ClasspathListenerHandler executeCommand " + commandId + ", " + arguments); @@ -130,15 +58,13 @@ public class ClasspathListenerHandler implements IDelegateCommandHandler { } private Object removeClasspathListener(String callbackCommandId) { - log("ClasspathListenerHandler addClasspathListener " + callbackCommandId); - subscribptions.unsubscribe(callbackCommandId); - log("ClasspathListenerHandler addClasspathListener " + callbackCommandId + " => OK"); - return "ok"; + log("ClasspathListenerHandler removeClasspathListener " + callbackCommandId); + return handlerImpl.removeClasspathListener(callbackCommandId); } private Object addClasspathListener(String callbackCommandId) { log("ClasspathListenerHandler addClasspathListener " + callbackCommandId); - subscribptions.subscribe(callbackCommandId); + handlerImpl.addClasspathListener(callbackCommandId); log("ClasspathListenerHandler addClasspathListener " + callbackCommandId + " => OK"); return "ok"; }