mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Include JSONRpc as a module project
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id 'java-library'
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = rootProject.ext.javaSourceVersion
|
||||
targetCompatibility = rootProject.ext.javaTargetVersion
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation google.gson
|
||||
implementation project (path: ':build-tools:logger')
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc;
|
||||
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
/**
|
||||
* Used for processing requests with cancellation support.
|
||||
*/
|
||||
public interface CancelChecker {
|
||||
|
||||
/**
|
||||
* Throw a {@link CancellationException} if the currently processed request
|
||||
* has been canceled.
|
||||
*/
|
||||
void checkCanceled();
|
||||
|
||||
/**
|
||||
* Check for cancellation without throwing an exception.
|
||||
*/
|
||||
default boolean isCanceled() {
|
||||
try {
|
||||
checkCanceled();
|
||||
} catch (CancellationException ce) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc;
|
||||
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Function;
|
||||
|
||||
public final class CompletableFutures {
|
||||
private CompletableFutures() {}
|
||||
|
||||
/**
|
||||
* A utility method to create a {@link CompletableFuture} with cancellation support.
|
||||
*
|
||||
* @param code a function that accepts a {@link CancelChecker} and returns the to be computed value
|
||||
* @return a future
|
||||
*/
|
||||
public static <R> CompletableFuture<R> computeAsync(Function<CancelChecker, R> code) {
|
||||
CompletableFuture<CancelChecker> start = new CompletableFuture<>();
|
||||
CompletableFuture<R> result = start.thenApplyAsync(code);
|
||||
start.complete(new FutureCancelChecker(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility method to create a {@link CompletableFuture} with cancellation support.
|
||||
*
|
||||
* @param code a function that accepts a {@link CancelChecker} and returns the to be computed value
|
||||
* @return a future
|
||||
*/
|
||||
public static <R> CompletableFuture<R> computeAsync(Executor executor, Function<CancelChecker, R> code) {
|
||||
CompletableFuture<CancelChecker> start = new CompletableFuture<>();
|
||||
CompletableFuture<R> result = start.thenApplyAsync(code, executor);
|
||||
start.complete(new FutureCancelChecker(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
public static class FutureCancelChecker implements CancelChecker {
|
||||
|
||||
private final CompletableFuture<?> future;
|
||||
|
||||
public FutureCancelChecker(CompletableFuture<?> future) {
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkCanceled() {
|
||||
if (future.isCancelled())
|
||||
throw new CancellationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCanceled() {
|
||||
return future.isCancelled();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* An endpoint is a generic interface that accepts jsonrpc requests and notifications.
|
||||
*/
|
||||
public interface Endpoint {
|
||||
|
||||
CompletableFuture<?> request(String method, Object parameter);
|
||||
|
||||
void notify(String method, Object parameter);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.SocketException;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
|
||||
/**
|
||||
* An exception thrown when accessing the JSON-RPC communication channel fails.
|
||||
*/
|
||||
public class JsonRpcException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Whether the given exception indicates that the currently accessed stream has been closed.
|
||||
*/
|
||||
public static boolean indicatesStreamClosed(Throwable thr) {
|
||||
return thr instanceof InterruptedIOException
|
||||
|| thr instanceof ClosedChannelException
|
||||
|| thr instanceof IOException && "Pipe closed".equals(thr.getMessage())
|
||||
|| thr instanceof SocketException &&
|
||||
("Connection reset".equals(thr.getMessage()) ||
|
||||
"Socket closed".equals(thr.getMessage()) ||
|
||||
"Broken pipe (Write failed)".equals(thr.getMessage()))
|
||||
|| thr instanceof JsonRpcException && indicatesStreamClosed(thr.getCause());
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = -7952794305289314670L;
|
||||
|
||||
public JsonRpcException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,442 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016-2018 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.json.ConcurrentMessageProcessor;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.JsonRpcMethod;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.JsonRpcMethodProvider;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.StreamMessageConsumer;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
|
||||
import org.eclipse.lsp4j.jsonrpc.services.ServiceEndpoints;
|
||||
import org.eclipse.lsp4j.jsonrpc.validation.ReflectiveMessageValidator;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* This is the entry point for applications that use LSP4J. A Launcher does all the wiring that is necessary to connect
|
||||
* your endpoint via an input stream and an output stream.
|
||||
*
|
||||
* @param <T> remote service interface type
|
||||
*/
|
||||
public interface Launcher<T> {
|
||||
|
||||
/**
|
||||
* Create a new Launcher for a given local service object, a given remote interface and an input and output stream.
|
||||
*
|
||||
* @param localService - the object that receives method calls from the remote service
|
||||
* @param remoteInterface - an interface on which RPC methods are looked up
|
||||
* @param in - input stream to listen for incoming messages
|
||||
* @param out - output stream to send outgoing messages
|
||||
*/
|
||||
static <T> Launcher<T> createLauncher(Object localService, Class<T> remoteInterface, InputStream in, OutputStream out) {
|
||||
return new Builder<T>()
|
||||
.setLocalService(localService)
|
||||
.setRemoteInterface(remoteInterface)
|
||||
.setInput(in)
|
||||
.setOutput(out)
|
||||
.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Launcher for a given local service object, a given remote interface and an input and output stream,
|
||||
* and set up message validation and tracing.
|
||||
*
|
||||
* @param localService - the object that receives method calls from the remote service
|
||||
* @param remoteInterface - an interface on which RPC methods are looked up
|
||||
* @param in - input stream to listen for incoming messages
|
||||
* @param out - output stream to send outgoing messages
|
||||
* @param validate - whether messages should be validated with the {@link ReflectiveMessageValidator}
|
||||
* @param trace - a writer to which incoming and outgoing messages are traced, or {@code null} to disable tracing
|
||||
*/
|
||||
static <T> Launcher<T> createLauncher(Object localService, Class<T> remoteInterface, InputStream in, OutputStream out,
|
||||
boolean validate, PrintWriter trace) {
|
||||
return new Builder<T>()
|
||||
.setLocalService(localService)
|
||||
.setRemoteInterface(remoteInterface)
|
||||
.setInput(in)
|
||||
.setOutput(out)
|
||||
.validateMessages(validate)
|
||||
.traceMessages(trace)
|
||||
.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Launcher for a given local service object, a given remote interface and an input and output stream.
|
||||
* Threads are started with the given executor service. The wrapper function is applied to the incoming and
|
||||
* outgoing message streams so additional message handling such as validation and tracing can be included.
|
||||
*
|
||||
* @param localService - the object that receives method calls from the remote service
|
||||
* @param remoteInterface - an interface on which RPC methods are looked up
|
||||
* @param in - input stream to listen for incoming messages
|
||||
* @param out - output stream to send outgoing messages
|
||||
* @param executorService - the executor service used to start threads
|
||||
* @param wrapper - a function for plugging in additional message consumers
|
||||
*/
|
||||
static <T> Launcher<T> createLauncher(Object localService, Class<T> remoteInterface, InputStream in, OutputStream out,
|
||||
ExecutorService executorService, Function<MessageConsumer, MessageConsumer> wrapper) {
|
||||
return createIoLauncher(localService, remoteInterface, in, out, executorService, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Launcher for a given local service object, a given remote interface and an input and output stream.
|
||||
* Threads are started with the given executor service. The wrapper function is applied to the incoming and
|
||||
* outgoing message streams so additional message handling such as validation and tracing can be included.
|
||||
*
|
||||
* @param localService - the object that receives method calls from the remote service
|
||||
* @param remoteInterface - an interface on which RPC methods are looked up
|
||||
* @param in - input stream to listen for incoming messages
|
||||
* @param out - output stream to send outgoing messages
|
||||
* @param executorService - the executor service used to start threads
|
||||
* @param wrapper - a function for plugging in additional message consumers
|
||||
*/
|
||||
static <T> Launcher<T> createIoLauncher(Object localService, Class<T> remoteInterface, InputStream in, OutputStream out,
|
||||
ExecutorService executorService, Function<MessageConsumer, MessageConsumer> wrapper) {
|
||||
return new Builder<T>()
|
||||
.setLocalService(localService)
|
||||
.setRemoteInterface(remoteInterface)
|
||||
.setInput(in)
|
||||
.setOutput(out)
|
||||
.setExecutorService(executorService)
|
||||
.wrapMessages(wrapper)
|
||||
.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Launcher for a given local service object, a given remote interface and an input and output stream.
|
||||
* Threads are started with the given executor service. The wrapper function is applied to the incoming and
|
||||
* outgoing message streams so additional message handling such as validation and tracing can be included.
|
||||
* The {@code configureGson} function can be used to register additional type adapters in the {@link GsonBuilder}
|
||||
* in order to support protocol classes that cannot be handled by Gson's reflective capabilities.
|
||||
*
|
||||
* @param localService - the object that receives method calls from the remote service
|
||||
* @param remoteInterface - an interface on which RPC methods are looked up
|
||||
* @param in - input stream to listen for incoming messages
|
||||
* @param out - output stream to send outgoing messages
|
||||
* @param executorService - the executor service used to start threads
|
||||
* @param wrapper - a function for plugging in additional message consumers
|
||||
* @param configureGson - a function for Gson configuration
|
||||
*/
|
||||
static <T> Launcher<T> createIoLauncher(Object localService, Class<T> remoteInterface, InputStream in, OutputStream out,
|
||||
ExecutorService executorService, Function<MessageConsumer, MessageConsumer> wrapper, Consumer<GsonBuilder> configureGson) {
|
||||
return new Builder<T>()
|
||||
.setLocalService(localService)
|
||||
.setRemoteInterface(remoteInterface)
|
||||
.setInput(in)
|
||||
.setOutput(out)
|
||||
.setExecutorService(executorService)
|
||||
.wrapMessages(wrapper)
|
||||
.configureGson(configureGson)
|
||||
.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Launcher for a given local service object, a given remote interface and an input and output stream.
|
||||
* Threads are started with the given executor service. The wrapper function is applied to the incoming and
|
||||
* outgoing message streams so additional message handling such as validation and tracing can be included.
|
||||
* The {@code configureGson} function can be used to register additional type adapters in the {@link GsonBuilder}
|
||||
* in order to support protocol classes that cannot be handled by Gson's reflective capabilities.
|
||||
*
|
||||
* @param localService - the object that receives method calls from the remote service
|
||||
* @param remoteInterface - an interface on which RPC methods are looked up
|
||||
* @param in - input stream to listen for incoming messages
|
||||
* @param out - output stream to send outgoing messages
|
||||
* @param validate - whether messages should be validated with the {@link ReflectiveMessageValidator}
|
||||
* @param executorService - the executor service used to start threads
|
||||
* @param wrapper - a function for plugging in additional message consumers
|
||||
* @param configureGson - a function for Gson configuration
|
||||
*/
|
||||
static <T> Launcher<T> createIoLauncher(Object localService, Class<T> remoteInterface, InputStream in, OutputStream out, boolean validate,
|
||||
ExecutorService executorService, Function<MessageConsumer, MessageConsumer> wrapper, Consumer<GsonBuilder> configureGson) {
|
||||
return new Builder<T>()
|
||||
.setLocalService(localService)
|
||||
.setRemoteInterface(remoteInterface)
|
||||
.setInput(in)
|
||||
.setOutput(out)
|
||||
.validateMessages(validate)
|
||||
.setExecutorService(executorService)
|
||||
.wrapMessages(wrapper)
|
||||
.configureGson(configureGson)
|
||||
.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Launcher for a collection of local service objects, a collection of remote interfaces and an
|
||||
* input and output stream. Threads are started with the given executor service. The wrapper function is applied
|
||||
* to the incoming and outgoing message streams so additional message handling such as validation and tracing
|
||||
* can be included. The {@code configureGson} function can be used to register additional type adapters in
|
||||
* the {@link GsonBuilder} in order to support protocol classes that cannot be handled by Gson's reflective
|
||||
* capabilities.
|
||||
*
|
||||
* @param localServices - the objects that receive method calls from the remote services
|
||||
* @param remoteInterfaces - interfaces on which RPC methods are looked up
|
||||
* @param classLoader - a class loader that is able to resolve all given interfaces
|
||||
* @param in - input stream to listen for incoming messages
|
||||
* @param out - output stream to send outgoing messages
|
||||
* @param executorService - the executor service used to start threads
|
||||
* @param wrapper - a function for plugging in additional message consumers
|
||||
* @param configureGson - a function for Gson configuration
|
||||
*/
|
||||
static Launcher<Object> createIoLauncher(Collection<Object> localServices, Collection<Class<?>> remoteInterfaces, ClassLoader classLoader,
|
||||
InputStream in, OutputStream out, ExecutorService executorService, Function<MessageConsumer, MessageConsumer> wrapper,
|
||||
Consumer<GsonBuilder> configureGson) {
|
||||
return new Builder<Object>()
|
||||
.setLocalServices(localServices)
|
||||
.setRemoteInterfaces(remoteInterfaces)
|
||||
.setClassLoader(classLoader)
|
||||
.setInput(in)
|
||||
.setOutput(out)
|
||||
.setExecutorService(executorService)
|
||||
.wrapMessages(wrapper)
|
||||
.configureGson(configureGson)
|
||||
.create();
|
||||
}
|
||||
|
||||
|
||||
//------------------------------ Builder Class ------------------------------//
|
||||
|
||||
/**
|
||||
* The launcher builder wires up all components for JSON-RPC communication.
|
||||
*
|
||||
* @param <T> remote service interface type
|
||||
*/
|
||||
public static class Builder<T> {
|
||||
|
||||
protected Collection<Object> localServices;
|
||||
protected Collection<Class<? extends T>> remoteInterfaces;
|
||||
protected InputStream input;
|
||||
protected OutputStream output;
|
||||
protected ExecutorService executorService;
|
||||
protected Function<MessageConsumer, MessageConsumer> messageWrapper;
|
||||
protected Function<Throwable, ResponseError> exceptionHandler;
|
||||
protected boolean validateMessages;
|
||||
protected Consumer<GsonBuilder> configureGson;
|
||||
protected ClassLoader classLoader;
|
||||
protected MessageTracer messageTracer;
|
||||
|
||||
public Builder<T> setLocalService(Object localService) {
|
||||
this.localServices = Collections.singletonList(localService);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> setLocalServices(Collection<Object> localServices) {
|
||||
this.localServices = localServices;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> setRemoteInterface(Class<? extends T> remoteInterface) {
|
||||
this.remoteInterfaces = Collections.singletonList(remoteInterface);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> setRemoteInterfaces(Collection<Class<? extends T>> remoteInterfaces) {
|
||||
this.remoteInterfaces = remoteInterfaces;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> setInput(InputStream input) {
|
||||
this.input = input;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> setOutput(OutputStream output) {
|
||||
this.output = output;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> setExecutorService(ExecutorService executorService) {
|
||||
this.executorService = executorService;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> setClassLoader(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> wrapMessages(Function<MessageConsumer, MessageConsumer> wrapper) {
|
||||
this.messageWrapper = wrapper;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> setExceptionHandler(Function<Throwable, ResponseError> exceptionHandler) {
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> validateMessages(boolean validate) {
|
||||
this.validateMessages = validate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> traceMessages(PrintWriter tracer) {
|
||||
if (tracer != null) {
|
||||
this.messageTracer = new MessageTracer(tracer);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> configureGson(Consumer<GsonBuilder> configureGson) {
|
||||
this.configureGson = configureGson;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Launcher<T> create() {
|
||||
// Validate input
|
||||
if (input == null)
|
||||
throw new IllegalStateException("Input stream must be configured.");
|
||||
if (output == null)
|
||||
throw new IllegalStateException("Output stream must be configured.");
|
||||
if (localServices == null)
|
||||
throw new IllegalStateException("Local service must be configured.");
|
||||
if (remoteInterfaces == null)
|
||||
throw new IllegalStateException("Remote interface must be configured.");
|
||||
|
||||
// Create the JSON handler, remote endpoint and remote proxy
|
||||
MessageJsonHandler jsonHandler = createJsonHandler();
|
||||
RemoteEndpoint remoteEndpoint = createRemoteEndpoint(jsonHandler);
|
||||
T remoteProxy = createProxy(remoteEndpoint);
|
||||
|
||||
// Create the message processor
|
||||
StreamMessageProducer reader = new StreamMessageProducer(input, jsonHandler, remoteEndpoint);
|
||||
MessageConsumer messageConsumer = wrapMessageConsumer(remoteEndpoint);
|
||||
ConcurrentMessageProcessor msgProcessor = createMessageProcessor(reader, messageConsumer, remoteProxy);
|
||||
ExecutorService execService = executorService != null ? executorService : Executors.newCachedThreadPool();
|
||||
return createLauncher(execService, remoteProxy, remoteEndpoint, msgProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the JSON handler for messages between the local and remote services.
|
||||
*/
|
||||
protected MessageJsonHandler createJsonHandler() {
|
||||
Map<String, JsonRpcMethod> supportedMethods = getSupportedMethods();
|
||||
if (configureGson != null)
|
||||
return new MessageJsonHandler(supportedMethods, configureGson);
|
||||
else
|
||||
return new MessageJsonHandler(supportedMethods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the remote endpoint that communicates with the local services.
|
||||
*/
|
||||
protected RemoteEndpoint createRemoteEndpoint(MessageJsonHandler jsonHandler) {
|
||||
MessageConsumer outgoingMessageStream = new StreamMessageConsumer(output, jsonHandler);
|
||||
outgoingMessageStream = wrapMessageConsumer(outgoingMessageStream);
|
||||
Endpoint localEndpoint = ServiceEndpoints.toEndpoint(localServices);
|
||||
RemoteEndpoint remoteEndpoint;
|
||||
if (exceptionHandler == null)
|
||||
remoteEndpoint = new RemoteEndpoint(outgoingMessageStream, localEndpoint);
|
||||
else
|
||||
remoteEndpoint = new RemoteEndpoint(outgoingMessageStream, localEndpoint, exceptionHandler);
|
||||
jsonHandler.setMethodProvider(remoteEndpoint);
|
||||
return remoteEndpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the proxy for calling methods on the remote service.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected T createProxy(RemoteEndpoint remoteEndpoint) {
|
||||
if (localServices.size() == 1 && remoteInterfaces.size() == 1) {
|
||||
return ServiceEndpoints.toServiceObject(remoteEndpoint, remoteInterfaces.iterator().next());
|
||||
} else {
|
||||
return (T) ServiceEndpoints.toServiceObject(remoteEndpoint, (Collection<Class<?>>) (Object) remoteInterfaces, classLoader);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the message processor that listens to the input stream.
|
||||
*/
|
||||
protected ConcurrentMessageProcessor createMessageProcessor(MessageProducer reader,
|
||||
MessageConsumer messageConsumer, T remoteProxy) {
|
||||
return new ConcurrentMessageProcessor(reader, messageConsumer);
|
||||
}
|
||||
|
||||
protected Launcher<T> createLauncher(ExecutorService execService, T remoteProxy, RemoteEndpoint remoteEndpoint,
|
||||
ConcurrentMessageProcessor msgProcessor) {
|
||||
return new StandardLauncher<T>(execService, remoteProxy, remoteEndpoint, msgProcessor);
|
||||
}
|
||||
|
||||
protected MessageConsumer wrapMessageConsumer(MessageConsumer consumer) {
|
||||
MessageConsumer result = consumer;
|
||||
if (messageTracer != null) {
|
||||
result = messageTracer.apply(consumer);
|
||||
}
|
||||
if (validateMessages) {
|
||||
result = new ReflectiveMessageValidator(result);
|
||||
}
|
||||
if (messageWrapper != null) {
|
||||
result = messageWrapper.apply(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather all JSON-RPC methods from the local and remote services.
|
||||
*/
|
||||
protected Map<String, JsonRpcMethod> getSupportedMethods() {
|
||||
Map<String, JsonRpcMethod> supportedMethods = new LinkedHashMap<>();
|
||||
// Gather the supported methods of remote interfaces
|
||||
for (Class<?> interface_ : remoteInterfaces) {
|
||||
supportedMethods.putAll(ServiceEndpoints.getSupportedMethods(interface_));
|
||||
}
|
||||
|
||||
// Gather the supported methods of local services
|
||||
for (Object localService : localServices) {
|
||||
if (localService instanceof JsonRpcMethodProvider) {
|
||||
JsonRpcMethodProvider rpcMethodProvider = (JsonRpcMethodProvider) localService;
|
||||
supportedMethods.putAll(rpcMethodProvider.supportedMethods());
|
||||
} else {
|
||||
supportedMethods.putAll(ServiceEndpoints.getSupportedMethods(localService.getClass()));
|
||||
}
|
||||
}
|
||||
|
||||
return supportedMethods;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//---------------------------- Interface Methods ----------------------------//
|
||||
|
||||
/**
|
||||
* Start a thread that listens to the input stream. The thread terminates when the stream is closed.
|
||||
*
|
||||
* @return a future that returns {@code null} when the listener thread is terminated
|
||||
*/
|
||||
Future<Void> startListening();
|
||||
|
||||
/**
|
||||
* Returns the proxy instance that implements the remote service interfaces.
|
||||
*/
|
||||
T getRemoteProxy();
|
||||
|
||||
/**
|
||||
* Returns the remote endpoint. Use this one to send generic {@code request} or {@code notify} methods
|
||||
* to the remote services.
|
||||
*/
|
||||
RemoteEndpoint getRemoteEndpoint();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Message;
|
||||
|
||||
public interface MessageConsumer {
|
||||
|
||||
/**
|
||||
* Consume a single message.
|
||||
*
|
||||
* @throws MessageIssueException when an issue is found that prevents further processing of the message
|
||||
* @throws JsonRpcException when accessing the JSON-RPC communication channel fails
|
||||
*/
|
||||
void consume(Message message) throws MessageIssueException, JsonRpcException;
|
||||
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Message;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.MessageIssue;
|
||||
|
||||
/**
|
||||
* An exception thrown to notify the caller of a {@link MessageConsumer} that one or more issues were
|
||||
* found while parsing or validating a message. This information can be passed to a {@link MessageIssueHandler}
|
||||
* in order to construct a proper response.
|
||||
*/
|
||||
public class MessageIssueException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1118210473728652711L;
|
||||
|
||||
private final Message rpcMessage;
|
||||
private final List<MessageIssue> issues;
|
||||
|
||||
public MessageIssueException(Message rpcMessage, MessageIssue issue) {
|
||||
this.rpcMessage = rpcMessage;
|
||||
this.issues = Collections.singletonList(issue);
|
||||
}
|
||||
|
||||
public MessageIssueException(Message rpcMessage, Collection<MessageIssue> issues) {
|
||||
this.rpcMessage = rpcMessage;
|
||||
this.issues = Collections.unmodifiableList(new ArrayList<>(issues));
|
||||
}
|
||||
|
||||
public MessageIssueException(Message rpcMessage, Stream<MessageIssue> issueStream) {
|
||||
this.rpcMessage = rpcMessage;
|
||||
this.issues = Collections.unmodifiableList(issueStream.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return issues.stream().map(issue -> issue.getText()).collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
public Message getRpcMessage() {
|
||||
return rpcMessage;
|
||||
}
|
||||
|
||||
public List<MessageIssue> getIssues() {
|
||||
return issues;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Message;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.MessageIssue;
|
||||
|
||||
public interface MessageIssueHandler {
|
||||
|
||||
/**
|
||||
* Handle issues found while parsing or validating a message. The list of issues must not be empty.
|
||||
*/
|
||||
void handle(Message message, List<MessageIssue> issues);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc;
|
||||
|
||||
public interface MessageProducer {
|
||||
|
||||
/**
|
||||
* Listen to a message source and forward all messages to the given consumer. Typically this method
|
||||
* blocks until the message source is unable to deliver more messages.
|
||||
*
|
||||
* @throws JsonRpcException when accessing the JSON-RPC communication channel fails
|
||||
*/
|
||||
void listen(MessageConsumer messageConsumer) throws JsonRpcException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016-2019 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.TracingMessageConsumer.RequestMetadata;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.time.Clock;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Wraps a {@link MessageConsumer} with one that logs in a way that the LSP Inspector can parse. *
|
||||
* https://microsoft.github.io/language-server-protocol/inspector/
|
||||
*/
|
||||
public class MessageTracer implements Function<MessageConsumer, MessageConsumer> {
|
||||
private final PrintWriter printWriter;
|
||||
private final Map<String, RequestMetadata> sentRequests = new HashMap<>();
|
||||
private final Map<String, RequestMetadata> receivedRequests = new HashMap<>();
|
||||
|
||||
MessageTracer(PrintWriter printWriter) {
|
||||
this.printWriter = Objects.requireNonNull(printWriter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageConsumer apply(MessageConsumer messageConsumer) {
|
||||
return new TracingMessageConsumer(
|
||||
messageConsumer, sentRequests, receivedRequests, printWriter, Clock.systemDefaultZone());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc;
|
||||
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.json.MessageConstants;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.MethodProvider;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.CancelParams;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Message;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.MessageIssue;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.NotificationMessage;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.RequestMessage;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* An endpoint that can be used to send messages to a given {@link MessageConsumer} by calling
|
||||
* {@link #request(String, Object)} or {@link #notify(String, Object)}. When connected to a {@link
|
||||
* MessageProducer}, this class forwards received messages to the local {@link Endpoint} given in
|
||||
* the constructor.
|
||||
*/
|
||||
public class RemoteEndpoint
|
||||
implements Endpoint, MessageConsumer, MessageIssueHandler, MethodProvider {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("RemoteEndpoint");
|
||||
|
||||
public static final Function<Throwable, ResponseError> DEFAULT_EXCEPTION_HANDLER =
|
||||
(throwable) -> {
|
||||
if (throwable instanceof ResponseErrorException) {
|
||||
return ((ResponseErrorException) throwable).getResponseError();
|
||||
} else if ((throwable instanceof CompletionException
|
||||
|| throwable instanceof InvocationTargetException)
|
||||
&& throwable.getCause() instanceof ResponseErrorException) {
|
||||
return ((ResponseErrorException) throwable.getCause()).getResponseError();
|
||||
} else {
|
||||
return fallbackResponseError("Internal error", throwable);
|
||||
}
|
||||
};
|
||||
private final MessageConsumer out;
|
||||
private final Endpoint localEndpoint;
|
||||
private final Function<Throwable, ResponseError> exceptionHandler;
|
||||
private final AtomicInteger nextRequestId = new AtomicInteger();
|
||||
private final Map<String, PendingRequestInfo> sentRequestMap = new LinkedHashMap<>();
|
||||
private final Map<String, CompletableFuture<?>> receivedRequestMap = new LinkedHashMap<>();
|
||||
/**
|
||||
* @param out - a consumer that transmits messages to the remote service
|
||||
* @param localEndpoint - the local service implementation
|
||||
*/
|
||||
public RemoteEndpoint(MessageConsumer out, Endpoint localEndpoint) {
|
||||
this(out, localEndpoint, DEFAULT_EXCEPTION_HANDLER);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param out - a consumer that transmits messages to the remote service
|
||||
* @param localEndpoint - the local service implementation
|
||||
* @param exceptionHandler - an exception handler that should never return null.
|
||||
*/
|
||||
public RemoteEndpoint(
|
||||
MessageConsumer out,
|
||||
Endpoint localEndpoint,
|
||||
Function<Throwable, ResponseError> exceptionHandler) {
|
||||
if (out == null) throw new NullPointerException("out");
|
||||
if (localEndpoint == null) throw new NullPointerException("localEndpoint");
|
||||
if (exceptionHandler == null) throw new NullPointerException("exceptionHandler");
|
||||
this.out = out;
|
||||
this.localEndpoint = localEndpoint;
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
}
|
||||
|
||||
private static ResponseError fallbackResponseError(String header, Throwable throwable) {
|
||||
LOG.error(header + ": " + throwable.getMessage(), throwable);
|
||||
ResponseError error = new ResponseError();
|
||||
error.setMessage(header + ".");
|
||||
error.setCode(ResponseErrorCode.InternalError);
|
||||
ByteArrayOutputStream stackTrace = new ByteArrayOutputStream();
|
||||
PrintWriter stackTraceWriter = new PrintWriter(stackTrace);
|
||||
throwable.printStackTrace(stackTraceWriter);
|
||||
stackTraceWriter.flush();
|
||||
error.setData(stackTrace.toString());
|
||||
return error;
|
||||
}
|
||||
|
||||
/** Send a request to the remote endpoint. */
|
||||
@Override
|
||||
public CompletableFuture<Object> request(String method, Object parameter) {
|
||||
final RequestMessage requestMessage = createRequestMessage(method, parameter);
|
||||
final CompletableFuture<Object> result =
|
||||
new CompletableFuture<Object>() {
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
sendCancelNotification(requestMessage.getRawId());
|
||||
return super.cancel(mayInterruptIfRunning);
|
||||
}
|
||||
};
|
||||
synchronized (sentRequestMap) {
|
||||
// Store request information so it can be handled when the response is received
|
||||
sentRequestMap.put(requestMessage.getId(), new PendingRequestInfo(requestMessage, result));
|
||||
}
|
||||
|
||||
try {
|
||||
// Send the request to the remote service
|
||||
out.consume(requestMessage);
|
||||
} catch (Exception exception) {
|
||||
// The message could not be sent, e.g. because the communication channel was closed
|
||||
result.completeExceptionally(exception);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Send a notification to the remote endpoint. */
|
||||
@Override
|
||||
public void notify(String method, Object parameter) {
|
||||
NotificationMessage notificationMessage = createNotificationMessage(method, parameter);
|
||||
try {
|
||||
out.consume(notificationMessage);
|
||||
} catch (Exception exception) {
|
||||
int logLevel =
|
||||
JsonRpcException.indicatesStreamClosed(exception) ? ILogger.INFO : ILogger.WARNING;
|
||||
LOG.log(logLevel, "Failed to send notification message.", exception);
|
||||
}
|
||||
}
|
||||
|
||||
protected RequestMessage createRequestMessage(String method, Object parameter) {
|
||||
RequestMessage requestMessage = new RequestMessage();
|
||||
requestMessage.setId(String.valueOf(nextRequestId.incrementAndGet()));
|
||||
requestMessage.setMethod(method);
|
||||
requestMessage.setParams(parameter);
|
||||
return requestMessage;
|
||||
}
|
||||
|
||||
protected void sendCancelNotification(Either<String, Number> id) {
|
||||
CancelParams cancelParams = new CancelParams();
|
||||
cancelParams.setRawId(id);
|
||||
notify(MessageJsonHandler.CANCEL_METHOD.getMethodName(), cancelParams);
|
||||
}
|
||||
|
||||
protected NotificationMessage createNotificationMessage(String method, Object parameter) {
|
||||
NotificationMessage notificationMessage = new NotificationMessage();
|
||||
notificationMessage.setJsonrpc(MessageConstants.JSONRPC_VERSION);
|
||||
notificationMessage.setMethod(method);
|
||||
notificationMessage.setParams(parameter);
|
||||
return notificationMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(Message message) {
|
||||
if (message instanceof NotificationMessage) {
|
||||
NotificationMessage notificationMessage = (NotificationMessage) message;
|
||||
handleNotification(notificationMessage);
|
||||
} else if (message instanceof RequestMessage) {
|
||||
RequestMessage requestMessage = (RequestMessage) message;
|
||||
handleRequest(requestMessage);
|
||||
} else if (message instanceof ResponseMessage) {
|
||||
ResponseMessage responseMessage = (ResponseMessage) message;
|
||||
handleResponse(responseMessage);
|
||||
} else {
|
||||
LOG.warn("Unkown message type.", message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Message message, List<MessageIssue> issues) {
|
||||
if (issues.isEmpty()) {
|
||||
throw new IllegalArgumentException("The list of issues must not be empty.");
|
||||
}
|
||||
|
||||
if (message instanceof RequestMessage) {
|
||||
RequestMessage requestMessage = (RequestMessage) message;
|
||||
handleRequestIssues(requestMessage, issues);
|
||||
} else if (message instanceof ResponseMessage) {
|
||||
ResponseMessage responseMessage = (ResponseMessage) message;
|
||||
handleResponseIssues(responseMessage, issues);
|
||||
} else {
|
||||
logIssues(message, issues);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleRequestIssues(RequestMessage requestMessage, List<MessageIssue> issues) {
|
||||
ResponseError errorObject = new ResponseError();
|
||||
if (issues.size() == 1) {
|
||||
MessageIssue issue = issues.get(0);
|
||||
errorObject.setMessage(issue.getText());
|
||||
errorObject.setCode(issue.getIssueCode());
|
||||
errorObject.setData(issue.getCause());
|
||||
} else {
|
||||
if (requestMessage.getMethod() != null)
|
||||
errorObject.setMessage(
|
||||
"Multiple issues were found in '" + requestMessage.getMethod() + "' request.");
|
||||
else errorObject.setMessage("Multiple issues were found in request.");
|
||||
errorObject.setCode(ResponseErrorCode.InvalidRequest);
|
||||
errorObject.setData(issues);
|
||||
}
|
||||
out.consume(createErrorResponseMessage(requestMessage, errorObject));
|
||||
}
|
||||
|
||||
protected void handleResponseIssues(ResponseMessage responseMessage, List<MessageIssue> issues) {
|
||||
PendingRequestInfo requestInfo;
|
||||
synchronized (sentRequestMap) {
|
||||
requestInfo = sentRequestMap.remove(responseMessage.getId());
|
||||
}
|
||||
if (requestInfo == null) {
|
||||
// We have no pending request information that matches the id given in the response
|
||||
LOG.warn("Unmatched response message: " + responseMessage);
|
||||
logIssues(responseMessage, issues);
|
||||
} else {
|
||||
requestInfo.future.completeExceptionally(new MessageIssueException(responseMessage, issues));
|
||||
}
|
||||
}
|
||||
|
||||
protected void logIssues(Message message, List<MessageIssue> issues) {
|
||||
for (MessageIssue issue : issues) {
|
||||
String logMessage =
|
||||
"Issue found in " + message.getClass().getSimpleName() + ": " + issue.getText();
|
||||
LOG.warn(logMessage, issue.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
protected ResponseMessage createErrorResponseMessage(
|
||||
RequestMessage requestMessage, ResponseError errorObject) {
|
||||
ResponseMessage responseMessage = createResponseMessage(requestMessage);
|
||||
responseMessage.setError(errorObject);
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
protected ResponseMessage createResponseMessage(RequestMessage requestMessage) {
|
||||
ResponseMessage responseMessage = new ResponseMessage();
|
||||
responseMessage.setRawId(requestMessage.getRawId());
|
||||
responseMessage.setJsonrpc(MessageConstants.JSONRPC_VERSION);
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolveMethod(String requestId) {
|
||||
synchronized (sentRequestMap) {
|
||||
PendingRequestInfo requestInfo = sentRequestMap.get(requestId);
|
||||
if (requestInfo != null) {
|
||||
return requestInfo.requestMessage.getMethod();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void handleResponse(ResponseMessage responseMessage) {
|
||||
PendingRequestInfo requestInfo;
|
||||
synchronized (sentRequestMap) {
|
||||
requestInfo = sentRequestMap.remove(responseMessage.getId());
|
||||
}
|
||||
if (requestInfo == null) {
|
||||
// We have no pending request information that matches the id given in the response
|
||||
LOG.warn("Unmatched response message: " + responseMessage);
|
||||
} else if (responseMessage.getError() != null) {
|
||||
// The remote service has replied with an error
|
||||
requestInfo.future.completeExceptionally(
|
||||
new ResponseErrorException(responseMessage.getError()));
|
||||
} else {
|
||||
// The remote service has replied with a result object
|
||||
requestInfo.future.complete(responseMessage.getResult());
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleNotification(NotificationMessage notificationMessage) {
|
||||
if (!handleCancellation(notificationMessage)) {
|
||||
// Forward the notification to the local endpoint
|
||||
try {
|
||||
localEndpoint.notify(notificationMessage.getMethod(), notificationMessage.getParams());
|
||||
} catch (Exception exception) {
|
||||
LOG.warn("Notification threw an exception: " + notificationMessage, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancellation is handled inside this class and not forwarded to the local endpoint.
|
||||
*
|
||||
* @return {@code true} if the given message is a cancellation notification, {@code false} if it
|
||||
* can be handled by the local endpoint
|
||||
*/
|
||||
protected boolean handleCancellation(NotificationMessage notificationMessage) {
|
||||
if (MessageJsonHandler.CANCEL_METHOD.getMethodName().equals(notificationMessage.getMethod())) {
|
||||
Object cancelParams = notificationMessage.getParams();
|
||||
if (cancelParams != null) {
|
||||
if (cancelParams instanceof CancelParams) {
|
||||
synchronized (receivedRequestMap) {
|
||||
String id = ((CancelParams) cancelParams).getId();
|
||||
CompletableFuture<?> future = receivedRequestMap.get(id);
|
||||
if (future != null) future.cancel(true);
|
||||
else LOG.warn("Unmatched cancel notification for request id " + id);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
LOG.warn(
|
||||
"Cancellation support is disabled, since the '"
|
||||
+ MessageJsonHandler.CANCEL_METHOD.getMethodName()
|
||||
+ "' method has been registered explicitly.");
|
||||
}
|
||||
} else {
|
||||
LOG.warn("Missing 'params' attribute of cancel notification.");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void handleRequest(RequestMessage requestMessage) {
|
||||
CompletableFuture<?> future;
|
||||
try {
|
||||
// Forward the request to the local endpoint
|
||||
future = localEndpoint.request(requestMessage.getMethod(), requestMessage.getParams());
|
||||
} catch (Throwable throwable) {
|
||||
// The local endpoint has failed handling the request - reply with an error response
|
||||
ResponseError errorObject = exceptionHandler.apply(throwable);
|
||||
if (errorObject == null) {
|
||||
errorObject =
|
||||
fallbackResponseError(
|
||||
"Internal error. Exception handler provided no error object", throwable);
|
||||
}
|
||||
out.consume(createErrorResponseMessage(requestMessage, errorObject));
|
||||
if (throwable instanceof Error) throw (Error) throwable;
|
||||
else return;
|
||||
}
|
||||
|
||||
final String messageId = requestMessage.getId();
|
||||
synchronized (receivedRequestMap) {
|
||||
receivedRequestMap.put(messageId, future);
|
||||
}
|
||||
future
|
||||
.thenAccept(
|
||||
(result) -> {
|
||||
// Reply with the result object that was computed by the local endpoint
|
||||
out.consume(createResultResponseMessage(requestMessage, result));
|
||||
})
|
||||
.exceptionally(
|
||||
(Throwable t) -> {
|
||||
// The local endpoint has failed computing a result - reply with an error response
|
||||
ResponseMessage responseMessage;
|
||||
if (isCancellation(t)) {
|
||||
String message =
|
||||
"The request (id: "
|
||||
+ messageId
|
||||
+ ", method: '"
|
||||
+ requestMessage.getMethod()
|
||||
+ "') has been cancelled";
|
||||
ResponseError errorObject =
|
||||
new ResponseError(ResponseErrorCode.RequestCancelled, message, null);
|
||||
responseMessage = createErrorResponseMessage(requestMessage, errorObject);
|
||||
} else {
|
||||
ResponseError errorObject = exceptionHandler.apply(t);
|
||||
if (errorObject == null) {
|
||||
errorObject =
|
||||
fallbackResponseError(
|
||||
"Internal error. Exception handler provided no error object", t);
|
||||
}
|
||||
responseMessage = createErrorResponseMessage(requestMessage, errorObject);
|
||||
}
|
||||
out.consume(responseMessage);
|
||||
return null;
|
||||
})
|
||||
.thenApply(
|
||||
(obj) -> {
|
||||
synchronized (receivedRequestMap) {
|
||||
receivedRequestMap.remove(messageId);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
protected ResponseMessage createResultResponseMessage(
|
||||
RequestMessage requestMessage, Object result) {
|
||||
ResponseMessage responseMessage = createResponseMessage(requestMessage);
|
||||
responseMessage.setResult(result);
|
||||
return responseMessage;
|
||||
}
|
||||
|
||||
protected boolean isCancellation(Throwable t) {
|
||||
if (t instanceof CompletionException) {
|
||||
return isCancellation(t.getCause());
|
||||
}
|
||||
return (t instanceof CancellationException);
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about requests that have been sent and for which no response has been received yet.
|
||||
*/
|
||||
private static class PendingRequestInfo {
|
||||
RequestMessage requestMessage;
|
||||
CompletableFuture<Object> future;
|
||||
|
||||
PendingRequestInfo(RequestMessage requestMessage2, CompletableFuture<Object> future2) {
|
||||
this.requestMessage = requestMessage2;
|
||||
this.future = future2;
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
|
||||
|
||||
/**
|
||||
* An exception thrown in order to send a response with an attached {@code error} object.
|
||||
*/
|
||||
public class ResponseErrorException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -5970739895395246885L;
|
||||
private ResponseError responseError;
|
||||
|
||||
public ResponseErrorException(ResponseError responseError) {
|
||||
this.responseError = responseError;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return responseError.getMessage();
|
||||
}
|
||||
|
||||
public ResponseError getResponseError() {
|
||||
return responseError;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018 Red Hat Inc
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.json.ConcurrentMessageProcessor;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer;
|
||||
|
||||
public class StandardLauncher<T> implements Launcher<T> {
|
||||
private final ExecutorService execService;
|
||||
private final T remoteProxy;
|
||||
private final RemoteEndpoint remoteEndpoint;
|
||||
private final ConcurrentMessageProcessor msgProcessor;
|
||||
|
||||
public StandardLauncher(StreamMessageProducer reader, MessageConsumer messageConsumer,
|
||||
ExecutorService execService, T remoteProxy, RemoteEndpoint remoteEndpoint) {
|
||||
this(execService, remoteProxy, remoteEndpoint,
|
||||
new ConcurrentMessageProcessor(reader, messageConsumer));
|
||||
}
|
||||
|
||||
public StandardLauncher(
|
||||
ExecutorService execService2, T remoteProxy2, RemoteEndpoint remoteEndpoint2,
|
||||
ConcurrentMessageProcessor msgProcessor) {
|
||||
this.execService = execService2;
|
||||
this.remoteProxy = remoteProxy2;
|
||||
this.remoteEndpoint = remoteEndpoint2;
|
||||
this.msgProcessor = msgProcessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<Void> startListening() {
|
||||
return msgProcessor.beginProcessing(execService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getRemoteProxy() {
|
||||
return remoteProxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteEndpoint getRemoteEndpoint() {
|
||||
return remoteEndpoint;
|
||||
}
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016-2019 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc;
|
||||
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.StreamMessageConsumer;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Message;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.NotificationMessage;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.RequestMessage;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A {@link MessageConsumer} that outputs logs in a format that can be parsed by the LSP Inspector.
|
||||
* https://microsoft.github.io/language-server-protocol/inspector/
|
||||
*/
|
||||
public class TracingMessageConsumer implements MessageConsumer {
|
||||
private static final ILogger LOG = ILogger.newInstance("TracingMessageConsumer");
|
||||
|
||||
private final MessageConsumer messageConsumer;
|
||||
private final Map<String, RequestMetadata> sentRequests;
|
||||
private final Map<String, RequestMetadata> receivedRequests;
|
||||
private final PrintWriter printWriter;
|
||||
private final Clock clock;
|
||||
private final DateTimeFormatter dateTimeFormatter;
|
||||
|
||||
/**
|
||||
* @param messageConsumer The {@link MessageConsumer} to wrap.
|
||||
* @param sentRequests A map that keeps track of pending sent request data.
|
||||
* @param receivedRequests A map that keeps track of pending received request data.
|
||||
* @param printWriter Where to write the log to.
|
||||
* @param clock The clock that is used to calculate timestamps and durations.
|
||||
*/
|
||||
public TracingMessageConsumer(
|
||||
MessageConsumer messageConsumer,
|
||||
Map<String, RequestMetadata> sentRequests,
|
||||
Map<String, RequestMetadata> receivedRequests,
|
||||
PrintWriter printWriter,
|
||||
Clock clock) {
|
||||
this(messageConsumer, sentRequests, receivedRequests, printWriter, clock, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param messageConsumer The {@link MessageConsumer} to wrap.
|
||||
* @param sentRequests A map that keeps track of pending sent request data.
|
||||
* @param receivedRequests A map that keeps track of pending received request data.
|
||||
* @param printWriter Where to write the log to.
|
||||
* @param clock The clock that is used to calculate timestamps and durations.
|
||||
* @param locale THe Locale to format the timestamps and durations, or <code>null</code> to use
|
||||
* default locale.
|
||||
*/
|
||||
public TracingMessageConsumer(
|
||||
MessageConsumer messageConsumer,
|
||||
Map<String, RequestMetadata> sentRequests,
|
||||
Map<String, RequestMetadata> receivedRequests,
|
||||
PrintWriter printWriter,
|
||||
Clock clock,
|
||||
Locale locale) {
|
||||
this.messageConsumer = Objects.requireNonNull(messageConsumer);
|
||||
this.sentRequests = Objects.requireNonNull(sentRequests);
|
||||
this.receivedRequests = Objects.requireNonNull(receivedRequests);
|
||||
this.printWriter = Objects.requireNonNull(printWriter);
|
||||
this.clock = Objects.requireNonNull(clock);
|
||||
if (locale == null) {
|
||||
this.dateTimeFormatter = DateTimeFormatter.ofPattern("KK:mm:ss a").withZone(clock.getZone());
|
||||
} else {
|
||||
this.dateTimeFormatter =
|
||||
DateTimeFormatter.ofPattern("KK:mm:ss a", locale).withZone(clock.getZone());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a log string for a given {@link Message}. The type of the {@link MessageConsumer}
|
||||
* determines if we're sending or receiving a message. The type of the @{link Message} determines
|
||||
* if it is a request, response, or notification.
|
||||
*/
|
||||
@Override
|
||||
public void consume(Message message) throws MessageIssueException, JsonRpcException {
|
||||
final Instant now = clock.instant();
|
||||
final String date = dateTimeFormatter.format(now);
|
||||
final String logString;
|
||||
|
||||
if (messageConsumer instanceof StreamMessageConsumer) {
|
||||
logString = consumeMessageSending(message, now, date);
|
||||
} else if (messageConsumer instanceof RemoteEndpoint) {
|
||||
logString = consumeMessageReceiving(message, now, date);
|
||||
} else {
|
||||
LOG.warn(String.format("Unknown MessageConsumer type: %s", messageConsumer));
|
||||
logString = null;
|
||||
}
|
||||
|
||||
if (logString != null) {
|
||||
printWriter.print(logString);
|
||||
printWriter.flush();
|
||||
}
|
||||
|
||||
messageConsumer.consume(message);
|
||||
}
|
||||
|
||||
private String consumeMessageSending(Message message, Instant now, String date) {
|
||||
if (message instanceof RequestMessage) {
|
||||
RequestMessage requestMessage = (RequestMessage) message;
|
||||
String id = requestMessage.getId();
|
||||
String method = requestMessage.getMethod();
|
||||
RequestMetadata requestMetadata = new RequestMetadata(method, now);
|
||||
sentRequests.put(id, requestMetadata);
|
||||
Object params = requestMessage.getParams();
|
||||
String paramsJson = MessageJsonHandler.toString(params);
|
||||
String format = "[Trace - %s] Sending request '%s - (%s)'\nParams: %s\n\n\n";
|
||||
return String.format(format, date, method, id, paramsJson);
|
||||
} else if (message instanceof ResponseMessage) {
|
||||
ResponseMessage responseMessage = (ResponseMessage) message;
|
||||
String id = responseMessage.getId();
|
||||
RequestMetadata requestMetadata = receivedRequests.remove(id);
|
||||
if (requestMetadata == null) {
|
||||
LOG.warn(String.format("Unmatched response message: %s", message));
|
||||
return null;
|
||||
}
|
||||
String method = requestMetadata.method;
|
||||
long latencyMillis = now.toEpochMilli() - requestMetadata.start.toEpochMilli();
|
||||
Object result = responseMessage.getResult();
|
||||
String resultJson = MessageJsonHandler.toString(result);
|
||||
String format =
|
||||
"[Trace - %s] Sending response '%s - (%s)'. Processing request took %sms\nResult: %s\n\n\n";
|
||||
return String.format(format, date, method, id, latencyMillis, resultJson);
|
||||
} else if (message instanceof NotificationMessage) {
|
||||
NotificationMessage notificationMessage = (NotificationMessage) message;
|
||||
String method = notificationMessage.getMethod();
|
||||
Object params = notificationMessage.getParams();
|
||||
String paramsJson = MessageJsonHandler.toString(params);
|
||||
String format = "[Trace - %s] Sending notification '%s'\nParams: %s\n\n\n";
|
||||
return String.format(format, date, method, paramsJson);
|
||||
} else {
|
||||
LOG.warn(String.format("Unknown message type: %s", message));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String consumeMessageReceiving(Message message, Instant now, String date) {
|
||||
if (message instanceof RequestMessage) {
|
||||
RequestMessage requestMessage = (RequestMessage) message;
|
||||
String method = requestMessage.getMethod();
|
||||
String id = requestMessage.getId();
|
||||
RequestMetadata requestMetadata = new RequestMetadata(method, now);
|
||||
receivedRequests.put(id, requestMetadata);
|
||||
Object params = requestMessage.getParams();
|
||||
String paramsJson = MessageJsonHandler.toString(params);
|
||||
String format = "[Trace - %s] Received request '%s - (%s)'\nParams: %s\n\n\n";
|
||||
return String.format(format, date, method, id, paramsJson);
|
||||
} else if (message instanceof ResponseMessage) {
|
||||
ResponseMessage responseMessage = (ResponseMessage) message;
|
||||
String id = responseMessage.getId();
|
||||
RequestMetadata requestMetadata = sentRequests.remove(id);
|
||||
if (requestMetadata == null) {
|
||||
LOG.warn(String.format("Unmatched response message: %s", message));
|
||||
return null;
|
||||
}
|
||||
String method = requestMetadata.method;
|
||||
long latencyMillis = now.toEpochMilli() - requestMetadata.start.toEpochMilli();
|
||||
Object result = responseMessage.getResult();
|
||||
String resultJson = MessageJsonHandler.toString(result);
|
||||
Object error = responseMessage.getError();
|
||||
String errorJson = MessageJsonHandler.toString(error);
|
||||
String format =
|
||||
"[Trace - %s] Received response '%s - (%s)' in %sms\nResult: %s\nError: %s\n\n\n";
|
||||
return String.format(format, date, method, id, latencyMillis, resultJson, errorJson);
|
||||
} else if (message instanceof NotificationMessage) {
|
||||
NotificationMessage notificationMessage = (NotificationMessage) message;
|
||||
String method = notificationMessage.getMethod();
|
||||
Object params = notificationMessage.getParams();
|
||||
String paramsJson = MessageJsonHandler.toString(params);
|
||||
String format = "[Trace - %s] Received notification '%s'\nParams: %s\n\n\n";
|
||||
return String.format(format, date, method, paramsJson);
|
||||
} else {
|
||||
LOG.warn(String.format("Unknown message type: %s", message));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Data class for holding pending request metadata. */
|
||||
public static class RequestMetadata {
|
||||
final String method;
|
||||
final Instant start;
|
||||
|
||||
public RequestMetadata(String method, Instant start) {
|
||||
this.method = method;
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(method, start);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
RequestMetadata that = (RequestMetadata) o;
|
||||
return Objects.equals(method, that.method) && Objects.equals(start, that.start);
|
||||
}
|
||||
}
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json;
|
||||
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
|
||||
import org.eclipse.lsp4j.jsonrpc.MessageProducer;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* This class connects a message producer with a message consumer by listening for new messages in a
|
||||
* dedicated thread.
|
||||
*/
|
||||
public class ConcurrentMessageProcessor implements Runnable {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("ConcurrentMessageProducer");
|
||||
private final MessageProducer messageProducer;
|
||||
private final MessageConsumer messageConsumer;
|
||||
private boolean isRunning;
|
||||
|
||||
public ConcurrentMessageProcessor(
|
||||
MessageProducer messageProducer, MessageConsumer messageConsumer) {
|
||||
this.messageProducer = messageProducer;
|
||||
this.messageConsumer = messageConsumer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a thread that listens for messages in the message producer and forwards them to the
|
||||
* message consumer.
|
||||
*
|
||||
* @param messageProducer - produces messages, e.g. by reading from an input channel
|
||||
* @param messageConsumer - processes messages and potentially forwards them to other consumers
|
||||
* @param executorService - the thread is started using this service
|
||||
* @return a future that is resolved when the started thread is terminated, e.g. by closing a
|
||||
* stream
|
||||
* @deprecated Please use the non-static ConcurrentMessageProcessor.beginProcessing() instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Future<Void> startProcessing(
|
||||
MessageProducer messageProducer,
|
||||
MessageConsumer messageConsumer,
|
||||
ExecutorService executorService) {
|
||||
ConcurrentMessageProcessor reader =
|
||||
new ConcurrentMessageProcessor(messageProducer, messageConsumer);
|
||||
final Future<?> result = executorService.submit(reader);
|
||||
return wrapFuture(result, messageProducer);
|
||||
}
|
||||
|
||||
public static Future<Void> wrapFuture(Future<?> result, MessageProducer messageProducer) {
|
||||
return new Future<Void>() {
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
if (mayInterruptIfRunning && messageProducer instanceof Closeable) {
|
||||
try {
|
||||
((Closeable) messageProducer).close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return result.cancel(mayInterruptIfRunning);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return result.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return result.isDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void get() throws InterruptedException, ExecutionException {
|
||||
return (Void) result.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void get(long timeout, TimeUnit unit)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return (Void) result.get(timeout, unit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a thread that listens for messages in the message producer and forwards them to the
|
||||
* message consumer.
|
||||
*
|
||||
* @param executorService - the thread is started using this service
|
||||
* @return a future that is resolved when the started thread is terminated, e.g. by closing a
|
||||
* stream
|
||||
*/
|
||||
public Future<Void> beginProcessing(ExecutorService executorService) {
|
||||
final Future<?> result = executorService.submit(this);
|
||||
return wrapFuture(result, messageProducer);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
processingStarted();
|
||||
try {
|
||||
messageProducer.listen(messageConsumer);
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
} finally {
|
||||
processingEnded();
|
||||
}
|
||||
}
|
||||
|
||||
protected void processingStarted() {
|
||||
if (isRunning) {
|
||||
throw new IllegalStateException("The message processor is already running.");
|
||||
}
|
||||
isRunning = true;
|
||||
}
|
||||
|
||||
protected void processingEnded() {
|
||||
isRunning = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
|
||||
/**
|
||||
* A description of a JSON-RPC method.
|
||||
*/
|
||||
public class JsonRpcMethod {
|
||||
|
||||
private final String methodName;
|
||||
private final Type[] parameterTypes;
|
||||
private final Type returnType;
|
||||
private final TypeAdapterFactory returnTypeAdapterFactory;
|
||||
private final boolean isNotification;
|
||||
|
||||
private JsonRpcMethod(String methodName, Type[] parameterTypes, Type returnType, TypeAdapterFactory returnTypeAdapterFactory,
|
||||
boolean isNotification) {
|
||||
if (methodName == null)
|
||||
throw new NullPointerException("methodName");
|
||||
this.methodName = methodName;
|
||||
this.parameterTypes = parameterTypes;
|
||||
this.returnType = returnType;
|
||||
this.returnTypeAdapterFactory = returnTypeAdapterFactory;
|
||||
this.isNotification = isNotification;
|
||||
}
|
||||
|
||||
public String getMethodName() {
|
||||
return methodName;
|
||||
}
|
||||
|
||||
public Type[] getParameterTypes() {
|
||||
return parameterTypes;
|
||||
}
|
||||
|
||||
public Type getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
public TypeAdapterFactory getReturnTypeAdapterFactory() {
|
||||
return returnTypeAdapterFactory;
|
||||
}
|
||||
|
||||
public boolean isNotification() {
|
||||
return isNotification;
|
||||
}
|
||||
|
||||
public static JsonRpcMethod notification(String name, Type... parameterTypes) {
|
||||
return new JsonRpcMethod(name, parameterTypes, Void.class, null, true);
|
||||
}
|
||||
|
||||
public static JsonRpcMethod request(String name, Type returnType, Type... parameterTypes) {
|
||||
return new JsonRpcMethod(name, parameterTypes, returnType, null, false);
|
||||
}
|
||||
|
||||
public static JsonRpcMethod request(String name, Type returnType, TypeAdapterFactory returnTypeAdapterFactory, Type... parameterTypes) {
|
||||
return new JsonRpcMethod(name, parameterTypes, returnType, returnTypeAdapterFactory, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (isNotification)
|
||||
builder.append("JsonRpcMethod (notification) {\n");
|
||||
else
|
||||
builder.append("JsonRpcMethod (request) {\n");
|
||||
builder.append("\tmethodName: ").append(methodName).append('\n');
|
||||
if (parameterTypes != null)
|
||||
builder.append("\tparameterTypes: ").append(parameterTypes).append('\n');
|
||||
if (returnType != null)
|
||||
builder.append("\treturnType: ").append(returnType).append('\n');
|
||||
builder.append("}");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.Endpoint;
|
||||
|
||||
/**
|
||||
* Provides {@link JsonRpcMethod}. Can be implemented by {@link Endpoint}s to
|
||||
* provide information about the supported methods.
|
||||
*/
|
||||
public interface JsonRpcMethodProvider {
|
||||
|
||||
Map<String, JsonRpcMethod> supportedMethods();
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json;
|
||||
|
||||
public interface MessageConstants {
|
||||
|
||||
public static String JSONRPC_VERSION = "2.0";
|
||||
public static String CONTENT_LENGTH_HEADER = "Content-Length";
|
||||
public static String CONTENT_TYPE_HEADER = "Content-Type";
|
||||
public static String JSON_MIME_TYPE = "application/json";
|
||||
public static String CRLF = "\r\n";
|
||||
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.MessageIssueException;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.adapters.CollectionTypeAdapter;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.adapters.EitherTypeAdapter;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.adapters.EnumTypeAdapter;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.adapters.MessageTypeAdapter;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.adapters.ThrowableTypeAdapter;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.adapters.TupleTypeAdapters;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.CancelParams;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Message;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.MessageIssue;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.MalformedJsonException;
|
||||
|
||||
/**
|
||||
* A wrapper around Gson that includes configuration required for JSON-RPC messages.
|
||||
*/
|
||||
public class MessageJsonHandler {
|
||||
|
||||
public static final JsonRpcMethod CANCEL_METHOD = JsonRpcMethod.notification("$/cancelRequest", CancelParams.class);
|
||||
|
||||
private final Gson gson;
|
||||
|
||||
private final Map<String, JsonRpcMethod> supportedMethods;
|
||||
|
||||
private MethodProvider methodProvider;
|
||||
|
||||
/**
|
||||
* @param supportedMethods - a map used to resolve RPC methods in {@link #getJsonRpcMethod(String)}
|
||||
*/
|
||||
public MessageJsonHandler(Map<String, JsonRpcMethod> supportedMethods) {
|
||||
this.supportedMethods = supportedMethods;
|
||||
this.gson = getDefaultGsonBuilder().create();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param supportedMethods - a map used to resolve RPC methods in {@link #getJsonRpcMethod(String)}
|
||||
* @param configureGson - a function that contributes to the GsonBuilder created by {@link #getDefaultGsonBuilder()}
|
||||
*/
|
||||
public MessageJsonHandler(Map<String, JsonRpcMethod> supportedMethods, Consumer<GsonBuilder> configureGson) {
|
||||
this.supportedMethods = supportedMethods;
|
||||
GsonBuilder gsonBuilder = getDefaultGsonBuilder();
|
||||
configureGson.accept(gsonBuilder);
|
||||
this.gson = gsonBuilder.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link GsonBuilder} with default settings for parsing JSON-RPC messages.
|
||||
*/
|
||||
public GsonBuilder getDefaultGsonBuilder() {
|
||||
return new GsonBuilder()
|
||||
.registerTypeAdapterFactory(new CollectionTypeAdapter.Factory())
|
||||
.registerTypeAdapterFactory(new ThrowableTypeAdapter.Factory())
|
||||
.registerTypeAdapterFactory(new EitherTypeAdapter.Factory())
|
||||
.registerTypeAdapterFactory(new TupleTypeAdapters.TwoTypeAdapterFactory())
|
||||
.registerTypeAdapterFactory(new EnumTypeAdapter.Factory())
|
||||
.registerTypeAdapterFactory(new MessageTypeAdapter.Factory(this));
|
||||
}
|
||||
|
||||
public Gson getGson() {
|
||||
return gson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an RPC method by name.
|
||||
*/
|
||||
public JsonRpcMethod getJsonRpcMethod(String name) {
|
||||
JsonRpcMethod result = supportedMethods.get(name);
|
||||
if (result != null)
|
||||
return result;
|
||||
else if (CANCEL_METHOD.getMethodName().equals(name))
|
||||
return CANCEL_METHOD;
|
||||
return null;
|
||||
}
|
||||
|
||||
public MethodProvider getMethodProvider() {
|
||||
return methodProvider;
|
||||
}
|
||||
|
||||
public void setMethodProvider(MethodProvider methodProvider) {
|
||||
this.methodProvider = methodProvider;
|
||||
}
|
||||
|
||||
public Message parseMessage(CharSequence input) throws JsonParseException {
|
||||
StringReader reader = new StringReader(input.toString());
|
||||
return parseMessage(reader);
|
||||
}
|
||||
|
||||
public Message parseMessage(Reader input) throws JsonParseException {
|
||||
JsonReader jsonReader = new JsonReader(input);
|
||||
Message message = gson.fromJson(jsonReader, Message.class);
|
||||
|
||||
if (message != null) {
|
||||
// Check whether the input has been fully consumed
|
||||
try {
|
||||
if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
|
||||
MessageIssue issue = new MessageIssue("JSON document was not fully consumed.", ResponseErrorCode.ParseError.getValue());
|
||||
throw new MessageIssueException(message, issue);
|
||||
}
|
||||
} catch (MalformedJsonException e) {
|
||||
MessageIssue issue = new MessageIssue("Message could not be parsed.", ResponseErrorCode.ParseError.getValue(), e);
|
||||
throw new MessageIssueException(message, issue);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
public String serialize(Message message) {
|
||||
StringWriter writer = new StringWriter();
|
||||
serialize(message, writer);
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
public void serialize(Message message, Writer output) throws JsonIOException {
|
||||
gson.toJson(message, Message.class, output);
|
||||
}
|
||||
|
||||
|
||||
private static MessageJsonHandler toStringInstance;
|
||||
|
||||
/**
|
||||
* Perform JSON serialization of the given object using the default configuration of JSON-RPC messages
|
||||
* enhanced with the pretty printing option.
|
||||
*/
|
||||
public static String toString(Object object) {
|
||||
if (toStringInstance == null) {
|
||||
toStringInstance = new MessageJsonHandler(Collections.emptyMap(), gsonBuilder -> {
|
||||
gsonBuilder.setPrettyPrinting();
|
||||
});
|
||||
}
|
||||
return toStringInstance.gson.toJson(object);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json;
|
||||
|
||||
public interface MethodProvider {
|
||||
|
||||
/**
|
||||
* Returns the method name for a given request id, or null if such request id is unknown.
|
||||
*
|
||||
* @return method name or {@code null}
|
||||
*/
|
||||
String resolveMethod(String requestId);
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
|
||||
/**
|
||||
* An annotation that indicates the Gson {@link TypeAdapterFactory} to use with a request response.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
public @interface ResponseJsonAdapter {
|
||||
|
||||
Class<? extends TypeAdapterFactory> value();
|
||||
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
|
||||
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Message;
|
||||
|
||||
/**
|
||||
* A message consumer that serializes messages to JSON and sends them to an output stream.
|
||||
*/
|
||||
public class StreamMessageConsumer implements MessageConsumer, MessageConstants {
|
||||
|
||||
private final String encoding;
|
||||
private final MessageJsonHandler jsonHandler;
|
||||
|
||||
private final Object outputLock = new Object();
|
||||
|
||||
private OutputStream output;
|
||||
|
||||
public StreamMessageConsumer(MessageJsonHandler jsonHandler) {
|
||||
this(null, StandardCharsets.UTF_8.name(), jsonHandler);
|
||||
}
|
||||
|
||||
public StreamMessageConsumer(OutputStream output, MessageJsonHandler jsonHandler) {
|
||||
this(output, StandardCharsets.UTF_8.name(), jsonHandler);
|
||||
}
|
||||
|
||||
public StreamMessageConsumer(OutputStream output, String encoding, MessageJsonHandler jsonHandler) {
|
||||
this.output = output;
|
||||
this.encoding = encoding;
|
||||
this.jsonHandler = jsonHandler;
|
||||
}
|
||||
|
||||
public OutputStream getOutput() {
|
||||
return output;
|
||||
}
|
||||
|
||||
public void setOutput(OutputStream output) {
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(Message message) {
|
||||
try {
|
||||
String content = jsonHandler.serialize(message);
|
||||
byte[] contentBytes = content.getBytes(encoding);
|
||||
int contentLength = contentBytes.length;
|
||||
|
||||
String header = getHeader(contentLength);
|
||||
byte[] headerBytes = header.getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
synchronized (outputLock) {
|
||||
output.write(headerBytes);
|
||||
output.write(contentBytes);
|
||||
output.flush();
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
throw new JsonRpcException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a header to be prepended to the actual content. This implementation writes
|
||||
* {@code Content-Length} and {@code Content-Type} attributes according to the LSP specification.
|
||||
*/
|
||||
protected String getHeader(int contentLength) {
|
||||
StringBuilder headerBuilder = new StringBuilder();
|
||||
appendHeader(headerBuilder, CONTENT_LENGTH_HEADER, contentLength).append(CRLF);
|
||||
if (!StandardCharsets.UTF_8.name().equals(encoding)) {
|
||||
appendHeader(headerBuilder, CONTENT_TYPE_HEADER, JSON_MIME_TYPE);
|
||||
headerBuilder.append("; charset=").append(encoding).append(CRLF);
|
||||
}
|
||||
headerBuilder.append(CRLF);
|
||||
return headerBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a header attribute to the given builder.
|
||||
*/
|
||||
protected StringBuilder appendHeader(StringBuilder builder, String name, Object value) {
|
||||
return builder.append(name).append(": ").append(value);
|
||||
}
|
||||
|
||||
}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json;
|
||||
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
|
||||
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
|
||||
import org.eclipse.lsp4j.jsonrpc.MessageIssueException;
|
||||
import org.eclipse.lsp4j.jsonrpc.MessageIssueHandler;
|
||||
import org.eclipse.lsp4j.jsonrpc.MessageProducer;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Message;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/** A message producer that reads from an input stream and parses messages from JSON. */
|
||||
public class StreamMessageProducer implements MessageProducer, Closeable, MessageConstants {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("StreamMessageProducer");
|
||||
|
||||
private final MessageJsonHandler jsonHandler;
|
||||
private final MessageIssueHandler issueHandler;
|
||||
|
||||
private InputStream input;
|
||||
|
||||
private MessageConsumer callback;
|
||||
private boolean keepRunning;
|
||||
|
||||
public StreamMessageProducer(InputStream input, MessageJsonHandler jsonHandler) {
|
||||
this(input, jsonHandler, null);
|
||||
}
|
||||
|
||||
public StreamMessageProducer(
|
||||
InputStream input, MessageJsonHandler jsonHandler, MessageIssueHandler issueHandler) {
|
||||
this.input = input;
|
||||
this.jsonHandler = jsonHandler;
|
||||
this.issueHandler = issueHandler;
|
||||
}
|
||||
|
||||
public InputStream getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
public void setInput(InputStream input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listen(MessageConsumer callback) {
|
||||
if (keepRunning) {
|
||||
throw new IllegalStateException("This StreamMessageProducer is already running.");
|
||||
}
|
||||
this.keepRunning = true;
|
||||
this.callback = callback;
|
||||
try {
|
||||
StringBuilder headerBuilder = null;
|
||||
StringBuilder debugBuilder = null;
|
||||
boolean newLine = false;
|
||||
Headers headers = new Headers();
|
||||
while (keepRunning) {
|
||||
int c = input.read();
|
||||
if (c == -1) {
|
||||
// End of input stream has been reached
|
||||
keepRunning = false;
|
||||
} else {
|
||||
if (debugBuilder == null) debugBuilder = new StringBuilder();
|
||||
debugBuilder.append((char) c);
|
||||
if (c == '\n') {
|
||||
if (newLine) {
|
||||
// Two consecutive newlines have been read, which signals the start of the message
|
||||
// content
|
||||
if (headers.contentLength < 0) {
|
||||
fireError(
|
||||
new IllegalStateException(
|
||||
"Missing header "
|
||||
+ CONTENT_LENGTH_HEADER
|
||||
+ " in input \""
|
||||
+ debugBuilder
|
||||
+ "\""));
|
||||
} else {
|
||||
boolean result = handleMessage(input, headers);
|
||||
if (!result) keepRunning = false;
|
||||
newLine = false;
|
||||
}
|
||||
headers = new Headers();
|
||||
debugBuilder = null;
|
||||
} else if (headerBuilder != null) {
|
||||
// A single newline ends a header line
|
||||
parseHeader(headerBuilder.toString(), headers);
|
||||
headerBuilder = null;
|
||||
}
|
||||
newLine = true;
|
||||
} else if (c != '\r') {
|
||||
// Add the input to the current header line
|
||||
if (headerBuilder == null) headerBuilder = new StringBuilder();
|
||||
headerBuilder.append((char) c);
|
||||
newLine = false;
|
||||
}
|
||||
}
|
||||
} // while (keepRunning)
|
||||
} catch (IOException exception) {
|
||||
if (JsonRpcException.indicatesStreamClosed(exception)) {
|
||||
// Only log the error if we had intended to keep running
|
||||
if (keepRunning) fireStreamClosed(exception);
|
||||
} else throw new JsonRpcException(exception);
|
||||
} finally {
|
||||
this.callback = null;
|
||||
this.keepRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Log an error. */
|
||||
protected void fireError(Throwable error) {
|
||||
String message =
|
||||
error.getMessage() != null
|
||||
? error.getMessage()
|
||||
: "An error occurred while processing an incoming message.";
|
||||
LOG.error(message, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the JSON content part of a message, parse it, and notify the callback.
|
||||
*
|
||||
* @return {@code true} if we should continue reading from the input stream, {@code false} if we
|
||||
* should stop
|
||||
*/
|
||||
protected boolean handleMessage(InputStream input, Headers headers) throws IOException {
|
||||
if (callback == null) callback = message -> LOG.info("Received message: " + message);
|
||||
|
||||
try {
|
||||
int contentLength = headers.contentLength;
|
||||
byte[] buffer = new byte[contentLength];
|
||||
int bytesRead = 0;
|
||||
|
||||
while (bytesRead < contentLength) {
|
||||
int readResult = input.read(buffer, bytesRead, contentLength - bytesRead);
|
||||
if (readResult == -1) return false;
|
||||
bytesRead += readResult;
|
||||
}
|
||||
|
||||
String content = new String(buffer, headers.charset);
|
||||
try {
|
||||
Message message = jsonHandler.parseMessage(content);
|
||||
callback.consume(message);
|
||||
} catch (MessageIssueException exception) {
|
||||
// An issue was found while parsing or validating the message
|
||||
if (issueHandler != null)
|
||||
issueHandler.handle(exception.getRpcMessage(), exception.getIssues());
|
||||
else fireError(exception);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
// UnsupportedEncodingException can be thrown by String constructor
|
||||
// JsonParseException can be thrown by jsonHandler
|
||||
// We also catch arbitrary exceptions that are thrown by message consumers in order to keep
|
||||
// this thread alive
|
||||
fireError(exception);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Parse a header attribute and set the corresponding data in the {@link Headers} fields. */
|
||||
protected void parseHeader(String line, Headers headers) {
|
||||
int sepIndex = line.indexOf(':');
|
||||
if (sepIndex >= 0) {
|
||||
String key = line.substring(0, sepIndex).trim();
|
||||
switch (key) {
|
||||
case CONTENT_LENGTH_HEADER:
|
||||
try {
|
||||
headers.contentLength = Integer.parseInt(line.substring(sepIndex + 1).trim());
|
||||
} catch (NumberFormatException e) {
|
||||
fireError(e);
|
||||
}
|
||||
break;
|
||||
case CONTENT_TYPE_HEADER:
|
||||
{
|
||||
int charsetIndex = line.indexOf("charset=");
|
||||
if (charsetIndex >= 0) headers.charset = line.substring(charsetIndex + 8).trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Report that the stream was closed through an exception. */
|
||||
protected void fireStreamClosed(Exception cause) {
|
||||
String message =
|
||||
cause.getMessage() != null ? cause.getMessage() : "The input stream was closed.";
|
||||
LOG.info(message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
keepRunning = false;
|
||||
}
|
||||
|
||||
protected static class Headers {
|
||||
public int contentLength = -1;
|
||||
public String charset = StandardCharsets.UTF_8.name();
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016-2017 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json.adapters;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* A specialized type adapter for collections that can handle single values.
|
||||
*/
|
||||
public class CollectionTypeAdapter<E> extends TypeAdapter<Collection<E>> {
|
||||
|
||||
public static class Factory implements TypeAdapterFactory {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
||||
if (!Collection.class.isAssignableFrom(typeToken.getRawType()))
|
||||
return null;
|
||||
|
||||
Type[] elementTypes = TypeUtils.getElementTypes(typeToken, Collection.class);
|
||||
if (elementTypes.length != 1)
|
||||
return null;
|
||||
Type elementType = elementTypes[0];
|
||||
TypeAdapter<?> elementTypeAdapter;
|
||||
if (elementType == Object.class)
|
||||
elementTypeAdapter = new JsonElementTypeAdapter(gson);
|
||||
else
|
||||
elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
|
||||
Supplier<Collection<Object>> constructor = getConstructor((Class<Collection<Object>>) typeToken.getRawType());
|
||||
return (TypeAdapter<T>) create(gson, elementType, elementTypeAdapter, constructor);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
protected TypeAdapter<?> create(Gson gson, Type elementType, TypeAdapter<?> elementTypeAdapter, Supplier<Collection<Object>> constructor) {
|
||||
return new CollectionTypeAdapter(gson, elementType, elementTypeAdapter, constructor);
|
||||
}
|
||||
|
||||
protected <E> Supplier<Collection<E>> getConstructor(Class<? extends Collection<E>> rawType) {
|
||||
try {
|
||||
Constructor<? extends Collection<E>> constructor = rawType.getDeclaredConstructor();
|
||||
return () -> {
|
||||
try {
|
||||
return constructor.newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new JsonParseException(e);
|
||||
}
|
||||
};
|
||||
} catch (Exception e) {
|
||||
if (SortedSet.class.isAssignableFrom(rawType))
|
||||
return () -> new TreeSet<E>();
|
||||
else if (Set.class.isAssignableFrom(rawType))
|
||||
return () -> new LinkedHashSet<E>();
|
||||
else if (Queue.class.isAssignableFrom(rawType))
|
||||
return () -> new LinkedList<E>();
|
||||
else
|
||||
return () -> new ArrayList<E>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final Gson gson;
|
||||
private final Type elementType;
|
||||
private final TypeAdapter<E> elementTypeAdapter;
|
||||
private final Supplier<Collection<E>> constructor;
|
||||
|
||||
public CollectionTypeAdapter(Gson gson, Type elementType, TypeAdapter<E> elementTypeAdapter, Supplier<Collection<E>> constructor) {
|
||||
this.gson = gson;
|
||||
this.elementType = elementType;
|
||||
this.elementTypeAdapter = elementTypeAdapter;
|
||||
this.constructor = constructor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<E> read(JsonReader in) throws IOException {
|
||||
JsonToken peek = in.peek();
|
||||
if (peek == JsonToken.NULL) {
|
||||
in.nextNull();
|
||||
return null;
|
||||
} else if (peek == JsonToken.BEGIN_ARRAY) {
|
||||
Collection<E> collection = constructor.get();
|
||||
in.beginArray();
|
||||
while (in.hasNext()) {
|
||||
E instance = elementTypeAdapter.read(in);
|
||||
collection.add(instance);
|
||||
}
|
||||
in.endArray();
|
||||
return collection;
|
||||
} else {
|
||||
Collection<E> collection = constructor.get();
|
||||
E instance = elementTypeAdapter.read(in);
|
||||
collection.add(instance);
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Collection<E> collection) throws IOException {
|
||||
if (collection == null) {
|
||||
out.nullValue();
|
||||
return;
|
||||
}
|
||||
out.beginArray();
|
||||
for (E element : collection) {
|
||||
if (element != null && elementType != element.getClass()
|
||||
&& (elementType instanceof TypeVariable<?> || elementType instanceof Class<?>)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
TypeAdapter<E> runtimeTypeAdapter = (TypeAdapter<E>) gson.getAdapter(TypeToken.get(element.getClass()));
|
||||
runtimeTypeAdapter.write(out, element);
|
||||
} else {
|
||||
elementTypeAdapter.write(out, element);
|
||||
}
|
||||
}
|
||||
out.endArray();
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json.adapters;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.TypeAdapter;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link CollectionTypeAdapter.Factory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class CollectionTypeAdapterFactory extends CollectionTypeAdapter.Factory {
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link CollectionTypeAdapter} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
protected static class Adapter<E> extends CollectionTypeAdapter<E> {
|
||||
|
||||
public Adapter(Gson gson, Type elementType, TypeAdapter<E> elementTypeAdapter,
|
||||
Supplier<Collection<E>> constructor) {
|
||||
super(gson, elementType, elementTypeAdapter, constructor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016-2018 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json.adapters;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either3;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Tuple;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* Type adapter for {@link Either} and {@link Either3}.
|
||||
*/
|
||||
public class EitherTypeAdapter<L, R> extends TypeAdapter<Either<L, R>> {
|
||||
|
||||
public static class Factory implements TypeAdapterFactory {
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@Override
|
||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
||||
if (!TypeUtils.isEither(typeToken.getType())) {
|
||||
return null;
|
||||
}
|
||||
return new EitherTypeAdapter(gson, typeToken);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A predicate that is usedful for checking alternatives in case both the left and the right type
|
||||
* are JSON object types.
|
||||
*/
|
||||
public static class PropertyChecker implements Predicate<JsonElement> {
|
||||
|
||||
private final String propertyName;
|
||||
private final String expectedValue;
|
||||
private final Class<? extends JsonElement> expectedType;
|
||||
|
||||
public PropertyChecker(String propertyName) {
|
||||
this.propertyName = propertyName;
|
||||
this.expectedValue = null;
|
||||
this.expectedType = null;
|
||||
}
|
||||
|
||||
public PropertyChecker(String propertyName, String expectedValue) {
|
||||
this.propertyName = propertyName;
|
||||
this.expectedValue = expectedValue;
|
||||
this.expectedType = null;
|
||||
}
|
||||
|
||||
public PropertyChecker(String propertyName, Class<? extends JsonElement> expectedType) {
|
||||
this.propertyName = propertyName;
|
||||
this.expectedType = expectedType;
|
||||
this.expectedValue = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(JsonElement element) {
|
||||
if (element.isJsonObject()) {
|
||||
JsonObject object = element.getAsJsonObject();
|
||||
JsonElement value = object.get(propertyName);
|
||||
if (expectedValue != null)
|
||||
return value != null && value.isJsonPrimitive() && expectedValue.equals(value.getAsString());
|
||||
else if (expectedType != null)
|
||||
return expectedType.isInstance(value);
|
||||
else
|
||||
return value != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A predicate for the case that a type alternative is a list.
|
||||
*/
|
||||
public static class ListChecker implements Predicate<JsonElement> {
|
||||
|
||||
private final Predicate<JsonElement> elementChecker;
|
||||
private final boolean resultIfEmpty;
|
||||
|
||||
public ListChecker(Predicate<JsonElement> elementChecker) {
|
||||
this(elementChecker, false);
|
||||
}
|
||||
|
||||
public ListChecker(Predicate<JsonElement> elementChecker, boolean resultIfEmpty) {
|
||||
this.elementChecker = elementChecker;
|
||||
this.resultIfEmpty = resultIfEmpty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(JsonElement t) {
|
||||
if (elementChecker.test(t))
|
||||
return true;
|
||||
if (t.isJsonArray()) {
|
||||
JsonArray array = t.getAsJsonArray();
|
||||
if (array.size() == 0)
|
||||
return resultIfEmpty;
|
||||
for (JsonElement e : array) {
|
||||
if (elementChecker.test(e))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected final TypeToken<Either<L, R>> typeToken;
|
||||
protected final EitherTypeArgument<L> left;
|
||||
protected final EitherTypeArgument<R> right;
|
||||
protected final Predicate<JsonElement> leftChecker;
|
||||
protected final Predicate<JsonElement> rightChecker;
|
||||
|
||||
public EitherTypeAdapter(Gson gson, TypeToken<Either<L, R>> typeToken) {
|
||||
this(gson, typeToken, null, null);
|
||||
}
|
||||
|
||||
public EitherTypeAdapter(Gson gson, TypeToken<Either<L, R>> typeToken, Predicate<JsonElement> leftChecker, Predicate<JsonElement> rightChecker) {
|
||||
this.typeToken = typeToken;
|
||||
Type[] elementTypes = TypeUtils.getElementTypes(typeToken, Either.class);
|
||||
this.left = new EitherTypeArgument<L>(gson, elementTypes[0]);
|
||||
this.right = new EitherTypeArgument<R>(gson, elementTypes[1]);
|
||||
this.leftChecker = leftChecker;
|
||||
this.rightChecker = rightChecker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Either<L, R> value) throws IOException {
|
||||
if (value == null) {
|
||||
out.nullValue();
|
||||
} else if (value.isLeft()) {
|
||||
left.write(out, value.getLeft());
|
||||
} else {
|
||||
right.write(out, value.getRight());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Either<L, R> read(JsonReader in) throws IOException {
|
||||
JsonToken next = in.peek();
|
||||
if (next == JsonToken.NULL) {
|
||||
in.nextNull();
|
||||
return null;
|
||||
}
|
||||
return create(next, in);
|
||||
}
|
||||
|
||||
protected Either<L, R> create(JsonToken nextToken, JsonReader in) throws IOException {
|
||||
boolean matchesLeft = left.isAssignable(nextToken);
|
||||
boolean matchesRight = right.isAssignable(nextToken);
|
||||
if (matchesLeft && matchesRight) {
|
||||
if (leftChecker != null || rightChecker != null) {
|
||||
JsonElement element = JsonParser.parseReader(in);
|
||||
if (leftChecker != null && leftChecker.test(element))
|
||||
// Parse the left alternative from the JSON element tree
|
||||
return createLeft(left.read(element));
|
||||
if (rightChecker != null && rightChecker.test(element))
|
||||
// Parse the right alternative from the JSON element tree
|
||||
return createRight(right.read(element));
|
||||
}
|
||||
throw new JsonParseException("Ambiguous Either type: token " + nextToken + " matches both alternatives.");
|
||||
} else if (matchesLeft) {
|
||||
// Parse the left alternative from the JSON stream
|
||||
return createLeft(left.read(in));
|
||||
} else if (matchesRight) {
|
||||
// Parse the right alternative from the JSON stream
|
||||
return createRight(right.read(in));
|
||||
} else if (leftChecker != null || rightChecker != null) {
|
||||
// If result is not the list but directly the only item in the list
|
||||
JsonElement element = JsonParser.parseReader(in);
|
||||
if (leftChecker != null && leftChecker.test(element))
|
||||
// Parse the left alternative from the JSON element tree
|
||||
return createLeft(left.read(element));
|
||||
if (rightChecker != null && rightChecker.test(element))
|
||||
// Parse the right alternative from the JSON element tree
|
||||
return createRight(right.read(element));
|
||||
}
|
||||
throw new JsonParseException("Unexpected token " + nextToken + ": expected " + left + " | " + right + " tokens.");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Either<L, R> createLeft(L obj) throws IOException {
|
||||
if (Either3.class.isAssignableFrom(typeToken.getRawType()))
|
||||
return (Either<L, R>) Either3.forLeft3(obj);
|
||||
return Either.forLeft(obj);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Either<L, R> createRight(R obj) throws IOException {
|
||||
if (Either3.class.isAssignableFrom(typeToken.getRawType()))
|
||||
return (Either<L, R>) Either3.forRight3((Either<?, ?>) obj);
|
||||
return Either.forRight(obj);
|
||||
}
|
||||
|
||||
protected static class EitherTypeArgument<T> {
|
||||
|
||||
protected final TypeToken<T> typeToken;
|
||||
protected final TypeAdapter<T> adapter;
|
||||
protected final Collection<JsonToken> expectedTokens;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public EitherTypeArgument(Gson gson, Type type) {
|
||||
this.typeToken = (TypeToken<T>) TypeToken.get(type);
|
||||
this.adapter = (type == Object.class) ? (TypeAdapter<T>) new JsonElementTypeAdapter(gson) : gson.getAdapter(this.typeToken);
|
||||
this.expectedTokens = new HashSet<>();
|
||||
for (Type expectedType : TypeUtils.getExpectedTypes(type)) {
|
||||
Class<?> rawType = TypeToken.get(expectedType).getRawType();
|
||||
JsonToken expectedToken = getExpectedToken(rawType);
|
||||
expectedTokens.add(expectedToken);
|
||||
}
|
||||
}
|
||||
|
||||
protected JsonToken getExpectedToken(Class<?> rawType) {
|
||||
if (rawType.isArray() || Collection.class.isAssignableFrom(rawType) || Tuple.class.isAssignableFrom(rawType)) {
|
||||
return JsonToken.BEGIN_ARRAY;
|
||||
}
|
||||
if (Boolean.class.isAssignableFrom(rawType)) {
|
||||
return JsonToken.BOOLEAN;
|
||||
}
|
||||
if (Number.class.isAssignableFrom(rawType) || Enum.class.isAssignableFrom(rawType)) {
|
||||
return JsonToken.NUMBER;
|
||||
}
|
||||
if (Character.class.isAssignableFrom(rawType) || String.class.isAssignableFrom(rawType)) {
|
||||
return JsonToken.STRING;
|
||||
}
|
||||
return JsonToken.BEGIN_OBJECT;
|
||||
}
|
||||
|
||||
public boolean isAssignable(JsonToken jsonToken) {
|
||||
return this.expectedTokens.contains(jsonToken);
|
||||
}
|
||||
|
||||
public void write(JsonWriter out, T value) throws IOException {
|
||||
this.adapter.write(out, value);
|
||||
}
|
||||
|
||||
public T read(JsonReader in) throws IOException {
|
||||
return this.adapter.read(in);
|
||||
}
|
||||
|
||||
public T read(JsonElement element) throws IOException {
|
||||
return this.adapter.fromJsonTree(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (JsonToken expectedToken : expectedTokens) {
|
||||
if (builder.length() != 0) {
|
||||
builder.append(" | ");
|
||||
}
|
||||
builder.append(expectedToken);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json.adapters;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link EitherTypeAdapter.Factory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class EitherTypeAdapterFactory extends EitherTypeAdapter.Factory {
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link EitherTypeAdapter} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
protected static class Adapter<L, R> extends EitherTypeAdapter<L, R> {
|
||||
|
||||
public Adapter(Gson gson, TypeToken<Either<L, R>> typeToken) {
|
||||
super(gson, typeToken);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016-2017 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json.adapters;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* A custom type adapter for enums that uses integer values.
|
||||
*/
|
||||
public class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
|
||||
|
||||
public static class Factory implements TypeAdapterFactory {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
||||
Class<?> rawType = typeToken.getRawType();
|
||||
if (!Enum.class.isAssignableFrom(rawType) || rawType == Enum.class)
|
||||
return null;
|
||||
if (!rawType.isEnum())
|
||||
rawType = rawType.getSuperclass();
|
||||
try {
|
||||
return new EnumTypeAdapter(rawType);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String VALUE_FIELD_NAME = "value";
|
||||
|
||||
private final Map<String, T> nameToConstant = new HashMap<>();
|
||||
private final Map<Integer, T> valueToConstant = new HashMap<>();
|
||||
private final Map<T, Integer> constantToValue = new HashMap<>();
|
||||
|
||||
EnumTypeAdapter(Class<T> classOfT) throws IllegalAccessException {
|
||||
try {
|
||||
Field valueField = classOfT.getDeclaredField(VALUE_FIELD_NAME);
|
||||
if (valueField.getType() != int.class && valueField.getType() != Integer.class)
|
||||
throw new IllegalArgumentException("The field 'value' must contain an integer value.");
|
||||
valueField.setAccessible(true);
|
||||
for (T constant : classOfT.getEnumConstants()) {
|
||||
nameToConstant.put(constant.name(), constant);
|
||||
Integer constValue = (Integer) valueField.get(constant);
|
||||
valueToConstant.put(constValue, constant);
|
||||
constantToValue.put(constant, constValue);
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
for (T constant : classOfT.getEnumConstants()) {
|
||||
nameToConstant.put(constant.name(), constant);
|
||||
int constValue = constant.ordinal();
|
||||
valueToConstant.put(constValue, constant);
|
||||
constantToValue.put(constant, constValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T read(JsonReader in) throws IOException {
|
||||
JsonToken peek = in.peek();
|
||||
if (peek == JsonToken.NULL) {
|
||||
in.nextNull();
|
||||
return null;
|
||||
} else if (peek == JsonToken.NUMBER) {
|
||||
return valueToConstant.get(in.nextInt());
|
||||
} else {
|
||||
String string = in.nextString();
|
||||
try {
|
||||
return valueToConstant.get(Integer.parseInt(string));
|
||||
} catch (NumberFormatException e) {
|
||||
return nameToConstant.get(string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, T value) throws IOException {
|
||||
if (value != null)
|
||||
out.value(constantToValue.get(value));
|
||||
else
|
||||
out.value((String) null);
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json.adapters;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link EnumTypeAdapter.Factory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class EnumTypeAdapterFactory extends EnumTypeAdapter.Factory {
|
||||
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json.adapters;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* A type adapter that reads every input into a tree of {@link JsonElement}s.
|
||||
*/
|
||||
public class JsonElementTypeAdapter extends TypeAdapter<Object> {
|
||||
|
||||
/**
|
||||
* This factory should not be registered with a GsonBuilder because it always matches.
|
||||
* Use it as argument to a {@link com.google.gson.annotations.JsonAdapter} annotation like this:
|
||||
* {@code @JsonAdapter(JsonElementTypeAdapter.Factory.class)}
|
||||
*/
|
||||
public static class Factory implements TypeAdapterFactory {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||
return (TypeAdapter<T>) new JsonElementTypeAdapter(gson);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final Gson gson;
|
||||
private final TypeAdapter<JsonElement> adapter;
|
||||
|
||||
public JsonElementTypeAdapter(Gson gson) {
|
||||
this.gson = gson;
|
||||
this.adapter = gson.getAdapter(JsonElement.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement read(JsonReader in) throws IOException {
|
||||
return adapter.read(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Object value) throws IOException {
|
||||
if (value == null) {
|
||||
out.nullValue();
|
||||
} else if (value instanceof JsonElement) {
|
||||
adapter.write(out, (JsonElement) value);
|
||||
} else {
|
||||
gson.toJson(value, value.getClass(), out);;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+459
@@ -0,0 +1,459 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016-2018 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json.adapters;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.MessageIssueException;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.JsonRpcMethod;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.MessageConstants;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.MethodProvider;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Message;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.MessageIssue;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.NotificationMessage;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.RequestMessage;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.JsonNull;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import com.google.gson.stream.MalformedJsonException;
|
||||
|
||||
/**
|
||||
* The type adapter for messages dispatches between the different message types: {@link RequestMessage},
|
||||
* {@link ResponseMessage}, and {@link NotificationMessage}.
|
||||
*/
|
||||
public class MessageTypeAdapter extends TypeAdapter<Message> {
|
||||
|
||||
public static class Factory implements TypeAdapterFactory {
|
||||
|
||||
private final MessageJsonHandler handler;
|
||||
|
||||
public Factory(MessageJsonHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
||||
if (!Message.class.isAssignableFrom(typeToken.getRawType()))
|
||||
return null;
|
||||
return (TypeAdapter<T>) new MessageTypeAdapter(handler, gson);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static Type[] EMPTY_TYPE_ARRAY = {};
|
||||
|
||||
private final MessageJsonHandler handler;
|
||||
private final Gson gson;
|
||||
|
||||
public MessageTypeAdapter(MessageJsonHandler handler, Gson gson) {
|
||||
this.handler = handler;
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message read(JsonReader in) throws IOException, JsonIOException, JsonSyntaxException {
|
||||
if (in.peek() == JsonToken.NULL) {
|
||||
in.nextNull();
|
||||
return null;
|
||||
}
|
||||
|
||||
in.beginObject();
|
||||
String jsonrpc = null, method = null;
|
||||
Either<String, Number> id = null;
|
||||
Object rawParams = null;
|
||||
Object rawResult = null;
|
||||
ResponseError responseError = null;
|
||||
try {
|
||||
|
||||
while (in.hasNext()) {
|
||||
String name = in.nextName();
|
||||
switch (name) {
|
||||
case "jsonrpc": {
|
||||
jsonrpc = in.nextString();
|
||||
break;
|
||||
}
|
||||
case "id": {
|
||||
if (in.peek() == JsonToken.NUMBER)
|
||||
id = Either.forRight(in.nextInt());
|
||||
else
|
||||
id = Either.forLeft(in.nextString());
|
||||
break;
|
||||
}
|
||||
case "method": {
|
||||
method = in.nextString();
|
||||
break;
|
||||
}
|
||||
case "params": {
|
||||
rawParams = parseParams(in, method);
|
||||
break;
|
||||
}
|
||||
case "result": {
|
||||
rawResult = parseResult(in, id != null ? id.get().toString() : null);
|
||||
break;
|
||||
}
|
||||
case "error": {
|
||||
responseError = gson.fromJson(in, ResponseError.class);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
in.skipValue();
|
||||
}
|
||||
}
|
||||
Object params = parseParams(rawParams, method);
|
||||
Object result = parseResult(rawResult, id != null ? id.get().toString() : null);
|
||||
|
||||
in.endObject();
|
||||
return createMessage(jsonrpc, id, method, params, result, responseError);
|
||||
|
||||
} catch (JsonSyntaxException | MalformedJsonException | EOFException exception) {
|
||||
if (id != null || method != null) {
|
||||
// Create a message and bundle it to an exception with an issue that wraps the original exception
|
||||
Message message = createMessage(jsonrpc, id, method, rawParams, rawResult, responseError);
|
||||
MessageIssue issue = new MessageIssue("Message could not be parsed.", ResponseErrorCode.ParseError.getValue(), exception);
|
||||
throw new MessageIssueException(message, issue);
|
||||
} else {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the json input into the result object corresponding to the call made
|
||||
* by id.
|
||||
*
|
||||
* If the id is not known until after parsing, call
|
||||
* {@link #parseResult(Object, String)} on the return value of this call for a
|
||||
* second chance conversion.
|
||||
*
|
||||
* @param in
|
||||
* json input to read from
|
||||
* @param id
|
||||
* id of request message this is in response to
|
||||
* @return correctly typed object if the correct expected type can be
|
||||
* determined, or a JsonElement representing the result
|
||||
*/
|
||||
protected Object parseResult(JsonReader in, String id) throws JsonIOException, JsonSyntaxException {
|
||||
Type type = null;
|
||||
MethodProvider methodProvider = handler.getMethodProvider();
|
||||
if (methodProvider != null && id != null) {
|
||||
String resolvedMethod = methodProvider.resolveMethod(id);
|
||||
if (resolvedMethod != null) {
|
||||
JsonRpcMethod jsonRpcMethod = handler.getJsonRpcMethod(resolvedMethod);
|
||||
if (jsonRpcMethod != null) {
|
||||
type = jsonRpcMethod.getReturnType();
|
||||
if (jsonRpcMethod.getReturnTypeAdapterFactory() != null) {
|
||||
TypeAdapter<?> typeAdapter = jsonRpcMethod.getReturnTypeAdapterFactory().create(gson, TypeToken.get(type));
|
||||
try {
|
||||
if (typeAdapter != null)
|
||||
return typeAdapter.read(in);
|
||||
} catch (IOException exception) {
|
||||
throw new JsonIOException(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fromJson(in, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the JsonElement into the result object corresponding to the call made
|
||||
* by id. If the result is already converted, does nothing.
|
||||
*
|
||||
* @param result
|
||||
* json element to read from
|
||||
* @param id
|
||||
* id of request message this is in response to
|
||||
* @return correctly typed object if the correct expected type can be
|
||||
* determined, or result unmodified if no conversion can be done.
|
||||
*/
|
||||
protected Object parseResult(Object result, String id) throws JsonSyntaxException {
|
||||
if (result instanceof JsonElement) {
|
||||
// Type of result could not be resolved - try again with the parsed JSON tree
|
||||
Type type = null;
|
||||
MethodProvider methodProvider = handler.getMethodProvider();
|
||||
if (methodProvider != null) {
|
||||
String resolvedMethod = methodProvider.resolveMethod(id);
|
||||
if (resolvedMethod != null) {
|
||||
JsonRpcMethod jsonRpcMethod = handler.getJsonRpcMethod(resolvedMethod);
|
||||
if (jsonRpcMethod != null) {
|
||||
type = jsonRpcMethod.getReturnType();
|
||||
if (jsonRpcMethod.getReturnTypeAdapterFactory() != null) {
|
||||
TypeAdapter<?> typeAdapter = jsonRpcMethod.getReturnTypeAdapterFactory().create(gson, TypeToken.get(type));
|
||||
if (typeAdapter != null)
|
||||
return typeAdapter.fromJsonTree((JsonElement) result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fromJson((JsonElement) result, type);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the json input into the parameters object corresponding to the call
|
||||
* made by method.
|
||||
*
|
||||
* If the method is not known until after parsing, call
|
||||
* {@link #parseParams(Object, String)} on the return value of this call for a
|
||||
* second chance conversion.
|
||||
*
|
||||
* @param in
|
||||
* json input to read from
|
||||
* @param method
|
||||
* method name of request
|
||||
* @return correctly typed object if the correct expected type can be
|
||||
* determined, or a JsonElement representing the parameters
|
||||
*/
|
||||
protected Object parseParams(JsonReader in, String method) throws IOException, JsonIOException {
|
||||
JsonToken next = in.peek();
|
||||
if (next == JsonToken.NULL) {
|
||||
in.nextNull();
|
||||
return null;
|
||||
}
|
||||
Type[] parameterTypes = getParameterTypes(method);
|
||||
if (parameterTypes.length == 1) {
|
||||
return fromJson(in, parameterTypes[0]);
|
||||
}
|
||||
if (parameterTypes.length > 1 && next == JsonToken.BEGIN_ARRAY) {
|
||||
List<Object> parameters = new ArrayList<Object>(parameterTypes.length);
|
||||
int index = 0;
|
||||
in.beginArray();
|
||||
while (in.hasNext()) {
|
||||
Type parameterType = index < parameterTypes.length ? parameterTypes[index] : null;
|
||||
Object parameter = fromJson(in, parameterType);
|
||||
parameters.add(parameter);
|
||||
index++;
|
||||
}
|
||||
in.endArray();
|
||||
while (index < parameterTypes.length) {
|
||||
parameters.add(null);
|
||||
index++;
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
JsonElement rawParams = JsonParser.parseReader(in);
|
||||
if (method != null && parameterTypes.length == 0 && (
|
||||
rawParams.isJsonArray() && rawParams.getAsJsonArray().size() == 0
|
||||
|| rawParams.isJsonObject() && rawParams.getAsJsonObject().size() == 0)) {
|
||||
return null;
|
||||
}
|
||||
return rawParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the JsonElement into the parameters object corresponding to the call made
|
||||
* by method. If the result is already converted, does nothing.
|
||||
*
|
||||
* @param params
|
||||
* json element to read from
|
||||
* @param method
|
||||
* method name of request
|
||||
* @return correctly typed object if the correct expected type can be
|
||||
* determined, or params unmodified if no conversion can be done.
|
||||
*/
|
||||
protected Object parseParams(Object params, String method) {
|
||||
if (isNull(params)) {
|
||||
return null;
|
||||
}
|
||||
if (!(params instanceof JsonElement)) {
|
||||
return params;
|
||||
}
|
||||
JsonElement rawParams = (JsonElement) params;
|
||||
Type[] parameterTypes = getParameterTypes(method);
|
||||
if (parameterTypes.length == 1) {
|
||||
return fromJson(rawParams, parameterTypes[0]);
|
||||
}
|
||||
if (parameterTypes.length > 1 && rawParams instanceof JsonArray) {
|
||||
JsonArray array = (JsonArray) rawParams;
|
||||
List<Object> parameters = new ArrayList<Object>(Math.max(array.size(), parameterTypes.length));
|
||||
int index = 0;
|
||||
Iterator<JsonElement> iterator = array.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Type parameterType = index < parameterTypes.length ? parameterTypes[index] : null;
|
||||
Object parameter = fromJson(iterator.next(), parameterType);
|
||||
parameters.add(parameter);
|
||||
index++;
|
||||
}
|
||||
while (index < parameterTypes.length) {
|
||||
parameters.add(null);
|
||||
index++;
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
if (method != null && parameterTypes.length == 0 && (
|
||||
rawParams.isJsonArray() && rawParams.getAsJsonArray().size() == 0
|
||||
|| rawParams.isJsonObject() && rawParams.getAsJsonObject().size() == 0)) {
|
||||
return null;
|
||||
}
|
||||
return rawParams;
|
||||
}
|
||||
|
||||
protected Object fromJson(JsonReader in, Type type) throws JsonIOException {
|
||||
if (isNullOrVoidType(type)) {
|
||||
return JsonParser.parseReader(in);
|
||||
}
|
||||
return gson.fromJson(in, type);
|
||||
}
|
||||
|
||||
protected Object fromJson(JsonElement element, Type type) {
|
||||
if (isNull(element)) {
|
||||
return null;
|
||||
}
|
||||
if (isNullOrVoidType(type)) {
|
||||
return element;
|
||||
}
|
||||
Object value = gson.fromJson(element, type);
|
||||
if (isNull(value)) {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected boolean isNull(Object value) {
|
||||
return value == null || value instanceof JsonNull;
|
||||
}
|
||||
|
||||
protected boolean isNullOrVoidType(Type type) {
|
||||
return type == null || Void.class == type;
|
||||
}
|
||||
|
||||
protected Type[] getParameterTypes(String method) {
|
||||
if (method != null) {
|
||||
JsonRpcMethod jsonRpcMethod = handler.getJsonRpcMethod(method);
|
||||
if (jsonRpcMethod != null)
|
||||
return jsonRpcMethod.getParameterTypes();
|
||||
}
|
||||
return EMPTY_TYPE_ARRAY;
|
||||
}
|
||||
|
||||
protected Message createMessage(String jsonrpc, Either<String, Number> id, String method, Object params,
|
||||
Object responseResult, ResponseError responseError) throws JsonParseException {
|
||||
if (id != null && method != null) {
|
||||
RequestMessage message = new RequestMessage();
|
||||
message.setJsonrpc(jsonrpc);
|
||||
message.setRawId(id);
|
||||
message.setMethod(method);
|
||||
message.setParams(params);
|
||||
return message;
|
||||
} else if (id != null) {
|
||||
ResponseMessage message = new ResponseMessage();
|
||||
message.setJsonrpc(jsonrpc);
|
||||
message.setRawId(id);
|
||||
if (responseError != null)
|
||||
message.setError(responseError);
|
||||
else
|
||||
message.setResult(responseResult);
|
||||
return message;
|
||||
} else if (method != null) {
|
||||
NotificationMessage message = new NotificationMessage();
|
||||
message.setJsonrpc(jsonrpc);
|
||||
message.setMethod(method);
|
||||
message.setParams(params);
|
||||
return message;
|
||||
} else {
|
||||
throw new JsonParseException("Unable to identify the input message.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Message message) throws IOException {
|
||||
out.beginObject();
|
||||
out.name("jsonrpc");
|
||||
out.value(message.getJsonrpc() == null ? MessageConstants.JSONRPC_VERSION : message.getJsonrpc());
|
||||
|
||||
if (message instanceof RequestMessage) {
|
||||
RequestMessage requestMessage = (RequestMessage) message;
|
||||
out.name("id");
|
||||
writeId(out, requestMessage.getRawId());
|
||||
out.name("method");
|
||||
out.value(requestMessage.getMethod());
|
||||
out.name("params");
|
||||
Object params = requestMessage.getParams();
|
||||
if (params == null)
|
||||
writeNullValue(out);
|
||||
else
|
||||
gson.toJson(params, params.getClass(), out);
|
||||
} else if (message instanceof ResponseMessage) {
|
||||
ResponseMessage responseMessage = (ResponseMessage) message;
|
||||
out.name("id");
|
||||
writeId(out, responseMessage.getRawId());
|
||||
if (responseMessage.getError() != null) {
|
||||
out.name("error");
|
||||
gson.toJson(responseMessage.getError(), ResponseError.class, out);
|
||||
} else {
|
||||
out.name("result");
|
||||
Object result = responseMessage.getResult();
|
||||
if (result == null)
|
||||
writeNullValue(out);
|
||||
else
|
||||
gson.toJson(result, result.getClass(), out);
|
||||
}
|
||||
} else if (message instanceof NotificationMessage) {
|
||||
NotificationMessage notificationMessage = (NotificationMessage) message;
|
||||
out.name("method");
|
||||
out.value(notificationMessage.getMethod());
|
||||
out.name("params");
|
||||
Object params = notificationMessage.getParams();
|
||||
if (params == null)
|
||||
writeNullValue(out);
|
||||
else
|
||||
gson.toJson(params, params.getClass(), out);
|
||||
}
|
||||
|
||||
out.endObject();
|
||||
}
|
||||
|
||||
protected void writeId(JsonWriter out, Either<String, Number> id) throws IOException {
|
||||
if (id == null)
|
||||
writeNullValue(out);
|
||||
else if (id.isLeft())
|
||||
out.value(id.getLeft());
|
||||
else if (id.isRight())
|
||||
out.value(id.getRight());
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to write a {@code null} value even if the JSON writer is set to not serialize {@code null}.
|
||||
*/
|
||||
protected void writeNullValue(JsonWriter out) throws IOException {
|
||||
boolean previousSerializeNulls = out.getSerializeNulls();
|
||||
out.setSerializeNulls(true);
|
||||
out.nullValue();
|
||||
out.setSerializeNulls(previousSerializeNulls);
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json.adapters;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link MessageTypeAdapter.Factory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class MessageTypeAdapterFactory extends MessageTypeAdapter.Factory {
|
||||
|
||||
public MessageTypeAdapterFactory(MessageJsonHandler handler) {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json.adapters;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* A type adapter for {@link Throwable}. This is used to report issues to the sender of a request.
|
||||
*/
|
||||
public class ThrowableTypeAdapter extends TypeAdapter<Throwable> {
|
||||
|
||||
public static class Factory implements TypeAdapterFactory {
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
@Override
|
||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
||||
if (!Throwable.class.isAssignableFrom(typeToken.getRawType()))
|
||||
return null;
|
||||
|
||||
return (TypeAdapter<T>) new ThrowableTypeAdapter((TypeToken<Throwable>) typeToken);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final TypeToken<Throwable> typeToken;
|
||||
|
||||
public ThrowableTypeAdapter(TypeToken<Throwable> typeToken) {
|
||||
this.typeToken = typeToken;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Throwable read(JsonReader in) throws IOException {
|
||||
if (in.peek() == JsonToken.NULL) {
|
||||
in.nextNull();
|
||||
return null;
|
||||
}
|
||||
|
||||
in.beginObject();
|
||||
String message = null;
|
||||
Throwable cause = null;
|
||||
while (in.hasNext()) {
|
||||
String name = in.nextName();
|
||||
switch (name) {
|
||||
case "message": {
|
||||
message = in.nextString();
|
||||
break;
|
||||
}
|
||||
case "cause": {
|
||||
cause = read(in);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
in.skipValue();
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
try {
|
||||
Constructor<Throwable> constructor;
|
||||
if (message == null && cause == null) {
|
||||
constructor = (Constructor<Throwable>) typeToken.getRawType().getDeclaredConstructor();
|
||||
return constructor.newInstance();
|
||||
} else if (message == null) {
|
||||
constructor = (Constructor<Throwable>) typeToken.getRawType().getDeclaredConstructor(Throwable.class);
|
||||
return constructor.newInstance(cause);
|
||||
} else if (cause == null) {
|
||||
constructor = (Constructor<Throwable>) typeToken.getRawType().getDeclaredConstructor(String.class);
|
||||
return constructor.newInstance(message);
|
||||
} else {
|
||||
constructor = (Constructor<Throwable>) typeToken.getRawType().getDeclaredConstructor(String.class, Throwable.class);
|
||||
return constructor.newInstance(message, cause);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
if (message == null && cause == null)
|
||||
return new RuntimeException();
|
||||
else if (message == null)
|
||||
return new RuntimeException(cause);
|
||||
else if (cause == null)
|
||||
return new RuntimeException(message);
|
||||
else
|
||||
return new RuntimeException(message, cause);
|
||||
} catch (Exception e) {
|
||||
throw new JsonParseException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Throwable throwable) throws IOException {
|
||||
if (throwable == null) {
|
||||
out.nullValue();
|
||||
} else if (throwable.getMessage() == null && throwable.getCause() != null) {
|
||||
write(out, throwable.getCause());
|
||||
} else {
|
||||
out.beginObject();
|
||||
if (throwable.getMessage() != null) {
|
||||
out.name("message");
|
||||
out.value(throwable.getMessage());
|
||||
}
|
||||
if (shouldWriteCause(throwable)) {
|
||||
out.name("cause");
|
||||
write(out, throwable.getCause());
|
||||
}
|
||||
out.endObject();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldWriteCause(Throwable throwable) {
|
||||
Throwable cause = throwable.getCause();
|
||||
if (cause == null || cause.getMessage() == null || cause == throwable)
|
||||
return false;
|
||||
if (throwable.getMessage() != null && throwable.getMessage().contains(cause.getMessage()))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package org.eclipse.lsp4j.jsonrpc.json.adapters;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Tuple.Two;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
public final class TupleTypeAdapters {
|
||||
|
||||
private TupleTypeAdapters() {}
|
||||
|
||||
public static class TwoTypeAdapter<F extends Object, S extends Object> extends TypeAdapter<Two<F, S>> {
|
||||
|
||||
protected final TypeAdapter<F> first;
|
||||
protected final TypeAdapter<S> second;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public TwoTypeAdapter(Gson gson, TypeToken<Two<F, S>> typeToken) {
|
||||
Type[] elementTypes = TypeUtils.getElementTypes(typeToken, Two.class);
|
||||
this.first = gson.getAdapter((TypeToken<F>) TypeToken.get(elementTypes[0]));
|
||||
this.second = gson.getAdapter((TypeToken<S>) TypeToken.get(elementTypes[1]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final JsonWriter out, final Two<F, S> value) throws IOException {
|
||||
if (value == null) {
|
||||
out.nullValue();
|
||||
} else {
|
||||
out.beginArray();
|
||||
first.write(out, value.getFirst());
|
||||
second.write(out, value.getSecond());
|
||||
out.endArray();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Two<F, S> read(final JsonReader in) throws IOException {
|
||||
JsonToken next = in.peek();
|
||||
if (next == JsonToken.NULL) {
|
||||
in.nextNull();
|
||||
return null;
|
||||
}
|
||||
in.beginArray();
|
||||
F f = first.read(in);
|
||||
S s = second.read(in);
|
||||
Two<F, S> result = new Two<F, S>(f, s);
|
||||
in.endArray();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TwoTypeAdapterFactory implements TypeAdapterFactory {
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@Override
|
||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
||||
if (!TypeUtils.isTwoTuple(typeToken.getType())) {
|
||||
return null;
|
||||
}
|
||||
return new TwoTypeAdapter(gson, typeToken);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2017 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.json.adapters;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.lang.reflect.WildcardType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Tuple;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
/**
|
||||
* Utilities for handling types in the JSON parser / serializer.
|
||||
*/
|
||||
public final class TypeUtils {
|
||||
|
||||
private TypeUtils() {}
|
||||
|
||||
/**
|
||||
* Determine the actual type arguments of the given type token with regard to the given target type.
|
||||
*/
|
||||
public static Type[] getElementTypes(TypeToken<?> typeToken, Class<?> targetType) {
|
||||
return getElementTypes(typeToken.getType(), typeToken.getRawType(), targetType);
|
||||
}
|
||||
|
||||
private static Type[] getElementTypes(Type type, Class<?> rawType, Class<?> targetType) {
|
||||
if (targetType.equals(rawType) && type instanceof ParameterizedType) {
|
||||
Type mappedType;
|
||||
if (type instanceof ParameterizedTypeImpl)
|
||||
mappedType = type;
|
||||
else
|
||||
// Transform wildcards in the actual type arguments
|
||||
mappedType = getMappedType(type, Collections.emptyMap());
|
||||
return ((ParameterizedType) mappedType).getActualTypeArguments();
|
||||
}
|
||||
// Map the parameters of the raw type to the actual type arguments
|
||||
Map<String, Type> varMapping = createVariableMapping(type, rawType);
|
||||
if (targetType.isInterface()) {
|
||||
// Look for superinterfaces that extend the target interface
|
||||
Class<?>[] interfaces = rawType.getInterfaces();
|
||||
for (int i = 0; i < interfaces.length; i++) {
|
||||
if (Collection.class.isAssignableFrom(interfaces[i])) {
|
||||
Type genericInterface = rawType.getGenericInterfaces()[i];
|
||||
Type mappedInterface = getMappedType(genericInterface, varMapping);
|
||||
return getElementTypes(mappedInterface, interfaces[i], targetType);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!rawType.isInterface()) {
|
||||
// Visit the superclass if it extends the target class / implements the target interface
|
||||
Class<?> rawSupertype = rawType.getSuperclass();
|
||||
if (targetType.isAssignableFrom(rawSupertype)) {
|
||||
Type genericSuperclass = rawType.getGenericSuperclass();
|
||||
Type mappedSuperclass = getMappedType(genericSuperclass, varMapping);
|
||||
return getElementTypes(mappedSuperclass, rawSupertype, targetType);
|
||||
}
|
||||
}
|
||||
// No luck, return an array of Object types
|
||||
Type[] result = new Type[targetType.getTypeParameters().length];
|
||||
Arrays.fill(result, Object.class);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static <T> Map<String, Type> createVariableMapping(Type type, Class<T> rawType) {
|
||||
if (type instanceof ParameterizedType) {
|
||||
TypeVariable<Class<T>>[] vars = rawType.getTypeParameters();
|
||||
Type[] args = ((ParameterizedType) type).getActualTypeArguments();
|
||||
Map<String, Type> newVarMapping = new HashMap<>(capacity(vars.length));
|
||||
for (int i = 0; i < vars.length; i++) {
|
||||
Type actualType = Object.class;
|
||||
if (i < args.length) {
|
||||
actualType = args[i];
|
||||
if (actualType instanceof WildcardType)
|
||||
actualType = ((WildcardType) actualType).getUpperBounds()[0];
|
||||
}
|
||||
newVarMapping.put(vars[i].getName(), actualType);
|
||||
}
|
||||
return newVarMapping;
|
||||
}
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
private static int capacity(int expectedSize) {
|
||||
if (expectedSize < 3)
|
||||
return expectedSize + 1;
|
||||
else
|
||||
return expectedSize + expectedSize / 3;
|
||||
}
|
||||
|
||||
private static Type getMappedType(Type type, Map<String, Type> varMapping) {
|
||||
if (type instanceof TypeVariable) {
|
||||
String name = ((TypeVariable<?>) type).getName();
|
||||
if (varMapping.containsKey(name))
|
||||
return varMapping.get(name);
|
||||
}
|
||||
if (type instanceof WildcardType) {
|
||||
return getMappedType(((WildcardType) type).getUpperBounds()[0], varMapping);
|
||||
}
|
||||
if (type instanceof ParameterizedType) {
|
||||
ParameterizedType pt = (ParameterizedType) type;
|
||||
Type[] origArgs = pt.getActualTypeArguments();
|
||||
Type[] mappedArgs = new Type[origArgs.length];
|
||||
for (int i = 0; i < origArgs.length; i++) {
|
||||
mappedArgs[i] = getMappedType(origArgs[i], varMapping);
|
||||
}
|
||||
return new ParameterizedTypeImpl(pt, mappedArgs);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private static class ParameterizedTypeImpl implements ParameterizedType {
|
||||
|
||||
private final Type ownerType;
|
||||
private final Type rawType;
|
||||
private final Type[] actualTypeArguments;
|
||||
|
||||
ParameterizedTypeImpl(ParameterizedType original, Type[] typeArguments) {
|
||||
this(original.getOwnerType(), original.getRawType(), typeArguments);
|
||||
}
|
||||
|
||||
ParameterizedTypeImpl(Type ownerType, Type rawType, Type[] typeArguments) {
|
||||
this.ownerType = ownerType;
|
||||
this.rawType = rawType;
|
||||
this.actualTypeArguments = typeArguments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getOwnerType() {
|
||||
return ownerType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getRawType() {
|
||||
return rawType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type[] getActualTypeArguments() {
|
||||
return actualTypeArguments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
if (ownerType != null) {
|
||||
result.append(toString(ownerType));
|
||||
result.append('$');
|
||||
}
|
||||
result.append(toString(rawType));
|
||||
result.append('<');
|
||||
for (int i = 0; i < actualTypeArguments.length; i++) {
|
||||
if (i > 0)
|
||||
result.append(", ");
|
||||
result.append(toString(actualTypeArguments[i]));
|
||||
}
|
||||
result.append('>');
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private String toString(Type type) {
|
||||
if (type instanceof Class<?>)
|
||||
return ((Class<?>) type).getName();
|
||||
else
|
||||
return String.valueOf(type);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all possible types that can be expected when an element of the given type is parsed.
|
||||
* If the type satisfies {@link #isEither(Type)}, a list of the corresponding type arguments is returned,
|
||||
* otherwise a list containg the type itself is returned. Type parameters are <em>not</em> resolved
|
||||
* by this method (use {@link #getElementTypes(TypeToken, Class)} to get resolved parameters).
|
||||
*/
|
||||
public static Collection<Type> getExpectedTypes(Type type) {
|
||||
Collection<Type> result = new ArrayList<>();
|
||||
collectExpectedTypes(type, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void collectExpectedTypes(Type type, Collection<Type> types) {
|
||||
if (isEither(type)) {
|
||||
if (type instanceof ParameterizedType) {
|
||||
for (Type typeArgument : ((ParameterizedType) type).getActualTypeArguments()) {
|
||||
collectExpectedTypes(typeArgument, types);
|
||||
}
|
||||
}
|
||||
if (type instanceof Class) {
|
||||
for (Type typeParameter : ((Class<?>) type).getTypeParameters()) {
|
||||
collectExpectedTypes(typeParameter, types);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
types.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given type is Either.
|
||||
*/
|
||||
public static boolean isEither(Type type) {
|
||||
if (type instanceof ParameterizedType) {
|
||||
return isEither(((ParameterizedType) type).getRawType());
|
||||
}
|
||||
if (type instanceof Class) {
|
||||
return Either.class.isAssignableFrom((Class<?>) type);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given type is a two-tuple (pair).
|
||||
*/
|
||||
public static boolean isTwoTuple(Type type) {
|
||||
if (type instanceof ParameterizedType) {
|
||||
return isTwoTuple(((ParameterizedType) type).getRawType());
|
||||
}
|
||||
if (type instanceof Class) {
|
||||
return Tuple.Two.class.isAssignableFrom((Class<?>) type);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.messages;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler;
|
||||
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
|
||||
|
||||
/**
|
||||
* To cancel a request a notification message with the following properties is sent.
|
||||
*/
|
||||
public class CancelParams {
|
||||
|
||||
/**
|
||||
* The request id to cancel.
|
||||
*/
|
||||
@NonNull
|
||||
private Either<String, Number> id;
|
||||
|
||||
@NonNull
|
||||
public String getId() {
|
||||
if (id == null)
|
||||
return null;
|
||||
if (id.isLeft())
|
||||
return id.getLeft();
|
||||
if (id.isRight())
|
||||
return id.getRight().toString();
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Either<String, Number> getRawId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@NonNull String id) {
|
||||
this.id = Either.forLeft(id);
|
||||
}
|
||||
|
||||
public void setId(@NonNull int id) {
|
||||
this.id = Either.forRight(id);
|
||||
}
|
||||
|
||||
public void setRawId(@NonNull Either<String, Number> id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MessageJsonHandler.toString(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
CancelParams other = (CancelParams) obj;
|
||||
if (this.id == null) {
|
||||
if (other.id != null)
|
||||
return false;
|
||||
} else if (!this.id.equals(other.id))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.messages;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
|
||||
|
||||
/**
|
||||
* An either type maps union types in protocol specifications.
|
||||
*/
|
||||
public class Either<L, R> {
|
||||
|
||||
public static <L, R> Either<L, R> forLeft(@NonNull L left) {
|
||||
return new Either<>(left, null);
|
||||
}
|
||||
|
||||
public static <L, R> Either<L, R> forRight(@NonNull R right) {
|
||||
return new Either<>(null, right);
|
||||
}
|
||||
|
||||
private final L left;
|
||||
private final R right;
|
||||
|
||||
protected Either(L left, R right) {
|
||||
super();
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public L getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
public R getRight() {
|
||||
return right;
|
||||
}
|
||||
|
||||
public Object get() {
|
||||
if (left != null)
|
||||
return left;
|
||||
if (right != null)
|
||||
return right;
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isLeft() {
|
||||
return left != null;
|
||||
}
|
||||
|
||||
public boolean isRight() {
|
||||
return right != null;
|
||||
}
|
||||
|
||||
public <T> T map(
|
||||
@NonNull Function<? super L, ? extends T> mapLeft,
|
||||
@NonNull Function<? super R, ? extends T> mapRight) {
|
||||
if (isLeft()) {
|
||||
return mapLeft.apply(getLeft());
|
||||
}
|
||||
if (isRight()) {
|
||||
return mapRight.apply(getRight());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Either<?, ?>) {
|
||||
Either<?, ?> other = (Either<?, ?>) obj;
|
||||
return (this.left == other.left && this.right == other.right)
|
||||
|| (this.left != null && other.left != null && this.left.equals(other.left))
|
||||
|| (this.right != null && other.right != null && this.right.equals(other.right));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (this.left != null)
|
||||
return this.left.hashCode();
|
||||
if (this.right != null)
|
||||
return this.right.hashCode();
|
||||
return 0;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder("Either [").append(System.lineSeparator());
|
||||
builder.append(" left = ").append(left).append(System.lineSeparator());
|
||||
builder.append(" right = ").append(right).append(System.lineSeparator());
|
||||
return builder.append("]").toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a left disjoint type if the given type is either.
|
||||
*
|
||||
* @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#getElementTypes(Type, Class, Class)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static Type getLeftDisjointType(Type type) {
|
||||
if (isEither(type)) {
|
||||
if (type instanceof ParameterizedType) {
|
||||
final ParameterizedType parameterizedType = (ParameterizedType) type;
|
||||
return parameterizedType.getActualTypeArguments()[0];
|
||||
}
|
||||
if (type instanceof Class) {
|
||||
final Class<?> cls = (Class<?>) type;
|
||||
return cls.getTypeParameters()[0];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a right disjoint type if the given type is either.
|
||||
*
|
||||
* @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#getElementTypes(Type, Class, Class)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static Type getRightDisjointType(Type type) {
|
||||
if (isEither(type)) {
|
||||
if (type instanceof ParameterizedType) {
|
||||
final ParameterizedType parameterizedType = (ParameterizedType) type;
|
||||
return parameterizedType.getActualTypeArguments()[1];
|
||||
}
|
||||
if (type instanceof Class) {
|
||||
final Class<?> cls = (Class<?>) type;
|
||||
return cls.getTypeParameters()[1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all disjoint types.
|
||||
*
|
||||
* @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#getExpectedTypes(Type)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static Collection<Type> getAllDisjoinTypes(Type type) {
|
||||
return collectDisjoinTypes(type, new ArrayList<>());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
protected static Collection<Type> collectDisjoinTypes(Type type, Collection<Type> types) {
|
||||
if (isEither(type)) {
|
||||
if (type instanceof ParameterizedType) {
|
||||
return collectDisjoinTypes((ParameterizedType) type, types);
|
||||
}
|
||||
if (type instanceof Class) {
|
||||
return collectDisjoinTypes((Class<?>) type, types);
|
||||
}
|
||||
}
|
||||
types.add(type);
|
||||
return types;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
protected static Collection<Type> collectDisjoinTypes(ParameterizedType type, Collection<Type> types) {
|
||||
for (Type typeArgument : type.getActualTypeArguments()) {
|
||||
collectDisjoinTypes(typeArgument, types);
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
protected static Collection<Type> collectDisjoinTypes(Class<?> type, Collection<Type> types) {
|
||||
for (Type typeParameter : type.getTypeParameters()) {
|
||||
collectDisjoinTypes(typeParameter, types);
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given type is Either.
|
||||
*
|
||||
* @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#isEither(Type)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isEither(Type type) {
|
||||
if (type instanceof ParameterizedType) {
|
||||
return isEither((ParameterizedType) type);
|
||||
}
|
||||
if (type instanceof Class) {
|
||||
return isEither((Class<?>) type);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given type is Either.
|
||||
*
|
||||
* @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#isEither(Type)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isEither(ParameterizedType type) {
|
||||
return isEither(type.getRawType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given class is Either.
|
||||
*
|
||||
* @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#isEither(Type)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isEither(Class<?> cls) {
|
||||
return Either.class.isAssignableFrom(cls);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2017 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.messages;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
|
||||
|
||||
/**
|
||||
* Union type for three types.
|
||||
*/
|
||||
public class Either3<T1, T2, T3> extends Either<T1, Either<T2, T3>> {
|
||||
|
||||
public static <T1, T2, T3> Either3<T1, T2, T3> forFirst(@NonNull T1 first) {
|
||||
return new Either3<T1, T2, T3>(first, null);
|
||||
}
|
||||
|
||||
public static <T1, T2, T3> Either3<T1, T2, T3> forSecond(@NonNull T2 second) {
|
||||
return new Either3<T1, T2, T3>(null, new Either<T2, T3>(second, null));
|
||||
}
|
||||
|
||||
public static <T1, T2, T3> Either3<T1, T2, T3> forThird(@NonNull T3 third) {
|
||||
return new Either3<T1, T2, T3>(null, new Either<T2, T3>(null, third));
|
||||
}
|
||||
|
||||
public static <T1, T2, T3> Either3<T1, T2, T3> forLeft3(@NonNull T1 first) {
|
||||
return new Either3<T1, T2, T3>(first, null);
|
||||
}
|
||||
|
||||
public static <T1, T2, T3> Either3<T1, T2, T3> forRight3(@NonNull Either<T2, T3> right) {
|
||||
return new Either3<T1, T2, T3>(null, right);
|
||||
}
|
||||
|
||||
protected Either3(T1 left, Either<T2, T3> right) {
|
||||
super(left, right);
|
||||
}
|
||||
|
||||
public T1 getFirst() {
|
||||
return getLeft();
|
||||
}
|
||||
|
||||
public T2 getSecond() {
|
||||
Either<T2, T3> right = getRight();
|
||||
if (right == null)
|
||||
return null;
|
||||
else
|
||||
return right.getLeft();
|
||||
}
|
||||
|
||||
public T3 getThird() {
|
||||
Either<T2, T3> right = getRight();
|
||||
if (right == null)
|
||||
return null;
|
||||
else
|
||||
return right.getRight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get() {
|
||||
if (isRight())
|
||||
return getRight().get();
|
||||
return super.get();
|
||||
}
|
||||
|
||||
public boolean isFirst() {
|
||||
return isLeft();
|
||||
}
|
||||
|
||||
public boolean isSecond() {
|
||||
return isRight() && getRight().isLeft();
|
||||
}
|
||||
|
||||
public boolean isThird() {
|
||||
return isRight() && getRight().isRight();
|
||||
}
|
||||
|
||||
public <T> T map(
|
||||
@NonNull Function<? super T1, ? extends T> mapFirst,
|
||||
@NonNull Function<? super T2, ? extends T> mapSecond,
|
||||
@NonNull Function<? super T3, ? extends T> mapThird) {
|
||||
if (isFirst()) {
|
||||
return mapFirst.apply(getFirst());
|
||||
}
|
||||
if (isSecond()) {
|
||||
return mapSecond.apply(getSecond());
|
||||
}
|
||||
if (isThird()) {
|
||||
return mapThird.apply(getThird());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder("Either3 [").append(System.lineSeparator());
|
||||
builder.append(" first = ").append(getFirst()).append(System.lineSeparator());
|
||||
builder.append(" second = ").append(getSecond()).append(System.lineSeparator());
|
||||
builder.append(" third = ").append(getThird()).append(System.lineSeparator());
|
||||
return builder.append("]").toString();
|
||||
}
|
||||
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.messages;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
|
||||
|
||||
/**
|
||||
* A message with an {@code id} property.
|
||||
*/
|
||||
public abstract class IdentifiableMessage extends Message {
|
||||
|
||||
/**
|
||||
* The message identifier.
|
||||
*/
|
||||
@NonNull
|
||||
private Either<String, Number> id;
|
||||
|
||||
public String getId() {
|
||||
if (id == null)
|
||||
return null;
|
||||
if (id.isLeft())
|
||||
return id.getLeft();
|
||||
if (id.isRight())
|
||||
return id.getRight().toString();
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Either<String, Number> getRawId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = Either.forLeft(id);
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = Either.forRight(id);
|
||||
}
|
||||
|
||||
public void setRawId(@NonNull Either<String, Number> id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
if (!super.equals(obj))
|
||||
return false;
|
||||
IdentifiableMessage other = (IdentifiableMessage) obj;
|
||||
if (this.id == null) {
|
||||
if (other.id != null)
|
||||
return false;
|
||||
} else if (!this.id.equals(other.id))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.messages;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.json.MessageConstants;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler;
|
||||
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
|
||||
|
||||
/**
|
||||
* A general message as defined by JSON-RPC. The language server protocol always
|
||||
* uses "2.0" as the jsonrpc version.
|
||||
*/
|
||||
public abstract class Message {
|
||||
|
||||
@NonNull
|
||||
private String jsonrpc = MessageConstants.JSONRPC_VERSION;
|
||||
|
||||
@NonNull
|
||||
public String getJsonrpc() {
|
||||
return this.jsonrpc;
|
||||
}
|
||||
|
||||
public void setJsonrpc(@NonNull String jsonrpc) {
|
||||
this.jsonrpc = jsonrpc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MessageJsonHandler.toString(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Message other = (Message) obj;
|
||||
if (this.jsonrpc == null) {
|
||||
if (other.jsonrpc != null)
|
||||
return false;
|
||||
} else if (!this.jsonrpc.equals(other.jsonrpc))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((this.jsonrpc == null) ? 0 : this.jsonrpc.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.messages;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
|
||||
|
||||
/**
|
||||
* Describes an issue found while parsing or validating a message.
|
||||
*/
|
||||
public class MessageIssue {
|
||||
|
||||
@NonNull
|
||||
private String text;
|
||||
|
||||
private int code;
|
||||
|
||||
private Exception cause;
|
||||
|
||||
public MessageIssue(@NonNull String text) {
|
||||
this(text, 0, null);
|
||||
}
|
||||
|
||||
public MessageIssue(@NonNull String text, int code) {
|
||||
this(text, code, null);
|
||||
}
|
||||
|
||||
public MessageIssue(@NonNull String text, int code, Exception cause) {
|
||||
this.text = text;
|
||||
this.code = code;
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public int getIssueCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public Exception getCause() {
|
||||
return cause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getText();
|
||||
}
|
||||
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.messages;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
|
||||
|
||||
/**
|
||||
* A notification message. A processed notification message must not send a
|
||||
* response back. They work like events.
|
||||
*/
|
||||
public class NotificationMessage extends Message {
|
||||
|
||||
/**
|
||||
* The method to be invoked.
|
||||
*/
|
||||
@NonNull
|
||||
private String method;
|
||||
|
||||
@NonNull
|
||||
public String getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
public void setMethod(@NonNull String method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method's params.
|
||||
*/
|
||||
private Object params;
|
||||
|
||||
public Object getParams() {
|
||||
return this.params;
|
||||
}
|
||||
|
||||
public void setParams(Object params) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
if (!super.equals(obj))
|
||||
return false;
|
||||
NotificationMessage other = (NotificationMessage) obj;
|
||||
if (this.method == null) {
|
||||
if (other.method != null)
|
||||
return false;
|
||||
} else if (!this.method.equals(other.method))
|
||||
return false;
|
||||
if (this.params == null) {
|
||||
if (other.params != null)
|
||||
return false;
|
||||
} else if (!this.params.equals(other.params))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + ((this.method == null) ? 0 : this.method.hashCode());
|
||||
result = prime * result + ((this.params == null) ? 0 : this.params.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.messages;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
|
||||
|
||||
/**
|
||||
* A request message to describe a request between the client and the server.
|
||||
* Every processed request must send a response back to the sender of the
|
||||
* request.
|
||||
*/
|
||||
public class RequestMessage extends IdentifiableMessage {
|
||||
|
||||
/**
|
||||
* The method to be invoked.
|
||||
*/
|
||||
@NonNull
|
||||
private String method;
|
||||
|
||||
@NonNull
|
||||
public String getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
public void setMethod(@NonNull String method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method's parameters. The object type depends on the chosen method.
|
||||
*/
|
||||
private Object params;
|
||||
|
||||
public Object getParams() {
|
||||
return this.params;
|
||||
}
|
||||
|
||||
public void setParams(Object params) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
if (!super.equals(obj))
|
||||
return false;
|
||||
RequestMessage other = (RequestMessage) obj;
|
||||
if (this.method == null) {
|
||||
if (other.method != null)
|
||||
return false;
|
||||
} else if (!this.method.equals(other.method))
|
||||
return false;
|
||||
if (this.params == null) {
|
||||
if (other.params != null)
|
||||
return false;
|
||||
} else if (!this.params.equals(other.params))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + ((this.method == null) ? 0 : this.method.hashCode());
|
||||
result = prime * result + ((this.params == null) ? 0 : this.params.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.messages;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.adapters.JsonElementTypeAdapter;
|
||||
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
|
||||
public class ResponseError {
|
||||
|
||||
/**
|
||||
* A number indicating the error type that occurred.
|
||||
*/
|
||||
@NonNull
|
||||
private int code;
|
||||
|
||||
@NonNull
|
||||
public int getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public void setCode(@NonNull int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public void setCode(ResponseErrorCode code) {
|
||||
this.setCode(code.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* A string providing a short description of the error.
|
||||
*/
|
||||
@NonNull
|
||||
private String message;
|
||||
|
||||
@NonNull
|
||||
public String getMessage() {
|
||||
return this.message;
|
||||
}
|
||||
|
||||
public void setMessage(@NonNull String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Primitive or Structured value that contains additional information
|
||||
* about the error. Can be omitted.
|
||||
*/
|
||||
@JsonAdapter(JsonElementTypeAdapter.Factory.class)
|
||||
private Object data;
|
||||
|
||||
public Object getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public void setData(Object data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public ResponseError() {
|
||||
}
|
||||
|
||||
public ResponseError(ResponseErrorCode code, String message, Object data) {
|
||||
this(code.getValue(), message, data);
|
||||
}
|
||||
|
||||
public ResponseError(int code, String message, Object data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MessageJsonHandler.toString(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
ResponseError other = (ResponseError) obj;
|
||||
if (other.code != this.code)
|
||||
return false;
|
||||
if (this.message == null) {
|
||||
if (other.message != null)
|
||||
return false;
|
||||
} else if (!this.message.equals(other.message))
|
||||
return false;
|
||||
if (this.data == null) {
|
||||
if (other.data != null)
|
||||
return false;
|
||||
} else if (!this.data.equals(other.data))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + this.code;
|
||||
result = prime * result + ((this.message == null) ? 0 : this.message.hashCode());
|
||||
result = prime * result + ((this.data == null) ? 0 : this.data.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.messages;
|
||||
|
||||
/**
|
||||
* A number indicating the error type that occurred.
|
||||
*/
|
||||
public enum ResponseErrorCode {
|
||||
|
||||
ParseError(-32700),
|
||||
|
||||
InvalidRequest(-32600),
|
||||
|
||||
MethodNotFound(-32601),
|
||||
|
||||
InvalidParams(-32602),
|
||||
|
||||
InternalError(-32603),
|
||||
|
||||
/**
|
||||
* This is the start range of JSON RPC reserved error codes.
|
||||
* It doesn't denote a real error code. No LSP error codes should
|
||||
* be defined between the start and end range. For backwards
|
||||
* compatibility the {@link #serverNotInitialized} and the
|
||||
* {@link #UnknownErrorCode} are left in the range.
|
||||
* <p>
|
||||
* Since 3.16.0
|
||||
*/
|
||||
jsonrpcReservedErrorRangeStart(-32099),
|
||||
|
||||
/** @deprecated use {@link #jsonrpcReservedErrorRangeStart} */
|
||||
@Deprecated
|
||||
serverErrorStart(-32099),
|
||||
|
||||
/**
|
||||
* Error code indicating that a server received a notification or
|
||||
* request before the server has received the {@code initialize} request.
|
||||
* <p>
|
||||
* Should be {@code ServerNotInitialized}
|
||||
*/
|
||||
serverNotInitialized(-32002),
|
||||
|
||||
UnknownErrorCode(-32001),
|
||||
|
||||
/**
|
||||
* This is the end range of JSON RPC reserved error codes.
|
||||
* It doesn't denote a real error code.
|
||||
* <p>
|
||||
* Since 3.16.0
|
||||
*/
|
||||
jsonrpcReservedErrorRangeEnd(-32000),
|
||||
|
||||
/** @deprecated use {@link #jsonrpcReservedErrorRangeEnd} */
|
||||
@Deprecated
|
||||
serverErrorEnd(-32000),
|
||||
|
||||
/**
|
||||
* This is the start range of LSP reserved error codes.
|
||||
* It doesn't denote a real error code.
|
||||
* <p>
|
||||
* Since 3.16.0
|
||||
*/
|
||||
lspReservedErrorRangeStart(-32899),
|
||||
|
||||
/**
|
||||
* A request failed but it was syntactically correct, e.g the
|
||||
* method name was known and the parameters were valid. The error
|
||||
* message should contain human readable information about why
|
||||
* the request failed.
|
||||
* <p>
|
||||
* Since 3.17.0
|
||||
*/
|
||||
RequestFailed(-32803),
|
||||
|
||||
/**
|
||||
* The server cancelled the request. This error code should
|
||||
* only be used for requests that explicitly support being
|
||||
* server cancellable.
|
||||
* <p>
|
||||
* Since 3.17.0
|
||||
*/
|
||||
ServerCancelled(-32802),
|
||||
|
||||
/**
|
||||
* The server detected that the content of a document got
|
||||
* modified outside normal conditions. A server should
|
||||
* NOT send this error code if it detects a content change
|
||||
* in it unprocessed messages. The result even computed
|
||||
* on an older state might still be useful for the client.
|
||||
* <p>
|
||||
* If a client decides that a result is not of any use anymore
|
||||
* the client should cancel the request.
|
||||
*/
|
||||
ContentModified(-32801),
|
||||
|
||||
/**
|
||||
* The client has canceled a request and a server as detected
|
||||
* the cancel.
|
||||
*/
|
||||
RequestCancelled(-32800),
|
||||
|
||||
/**
|
||||
* This is the end range of LSP reserved error codes.
|
||||
* It doesn't denote a real error code.
|
||||
* <p>
|
||||
* Since 3.16.0
|
||||
*/
|
||||
lspReservedErrorRangeEnd(-32800);
|
||||
|
||||
private final int value;
|
||||
|
||||
ResponseErrorCode(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.messages;
|
||||
|
||||
/**
|
||||
* Response message sent as a result of a request. If a request doesn't provide
|
||||
* a result value the receiver of a request still needs to return a response
|
||||
* message to conform to the JSON RPC specification. The result property of the
|
||||
* ResponseMessage should be set to null in this case to signal a successful
|
||||
* request. A response message is linked to a request via their {@code id} properties.
|
||||
*/
|
||||
public class ResponseMessage extends IdentifiableMessage {
|
||||
|
||||
/**
|
||||
* The result of a request. This can be omitted in the case of an error.
|
||||
* The object type depends on the method of the corresponding request.
|
||||
*/
|
||||
private Object result;
|
||||
|
||||
public Object getResult() {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
public void setResult(Object result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The error object in case a request fails.
|
||||
*/
|
||||
private ResponseError error;
|
||||
|
||||
public ResponseError getError() {
|
||||
return this.error;
|
||||
}
|
||||
|
||||
public void setError(ResponseError error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
if (!super.equals(obj))
|
||||
return false;
|
||||
ResponseMessage other = (ResponseMessage) obj;
|
||||
if (this.result == null) {
|
||||
if (other.result != null)
|
||||
return false;
|
||||
} else if (!this.result.equals(other.result))
|
||||
return false;
|
||||
if (this.error == null) {
|
||||
if (other.error != null)
|
||||
return false;
|
||||
} else if (!this.error.equals(other.error))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + ((this.result == null) ? 0 : this.result.hashCode());
|
||||
result = prime * result + ((this.error == null) ? 0 : this.error.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.eclipse.lsp4j.jsonrpc.messages;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* Representation of tuple types.
|
||||
*/
|
||||
@SuppressWarnings("all")
|
||||
public interface Tuple {
|
||||
|
||||
public static <F, S> Two<F, S> two(F first, S second) {
|
||||
return new Two<F, S>(first, second);
|
||||
}
|
||||
|
||||
/**
|
||||
* A two-tuple, i.e. a pair.
|
||||
*/
|
||||
public static class Two<F extends Object, S extends Object> implements Tuple {
|
||||
|
||||
private final F first;
|
||||
private final S second;
|
||||
|
||||
public Two(F first, S second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public F getFirst() {
|
||||
return this.first;
|
||||
}
|
||||
|
||||
public S getSecond() {
|
||||
return this.second;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Tuple.Two<?, ?> other = (Tuple.Two<?, ?>) obj;
|
||||
if (this.first == null) {
|
||||
if (other.first != null)
|
||||
return false;
|
||||
} else if (!this.first.equals(other.first))
|
||||
return false;
|
||||
if (this.second == null) {
|
||||
if (other.second != null)
|
||||
return false;
|
||||
} else if (!this.second.equals(other.second))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((this.first == null) ? 0 : this.first.hashCode());
|
||||
return prime * result + ((this.second == null) ? 0 : this.second.hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder("Tuples.Two [").append(System.lineSeparator());
|
||||
builder.append(" first = ").append(first).append(System.lineSeparator());
|
||||
builder.append(" second = ").append(second).append(System.lineSeparator());
|
||||
return builder.append("]").toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.services;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class AnnotationUtil {
|
||||
private AnnotationUtil() {}
|
||||
|
||||
public static void findDelegateSegments(Class<?> clazz, Set<Class<?>> visited, Consumer<Method> acceptor) {
|
||||
if (clazz == null || !visited.add(clazz))
|
||||
return;
|
||||
findDelegateSegments(clazz.getSuperclass(), visited, acceptor);
|
||||
for (Class<?> interf : clazz.getInterfaces()) {
|
||||
findDelegateSegments(interf, visited, acceptor);
|
||||
}
|
||||
for (Method method : clazz.getDeclaredMethods()) {
|
||||
if (isDelegateMethod(method)) {
|
||||
acceptor.accept(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDelegateMethod(Method method) {
|
||||
if (!method.isSynthetic()) {
|
||||
JsonDelegate jsonDelegate = method.getAnnotation(JsonDelegate.class);
|
||||
if (jsonDelegate != null) {
|
||||
if (!(method.getParameterCount() == 0 && method.getReturnType().isInterface())) {
|
||||
throw new IllegalStateException(
|
||||
"The method " + method.toString() + " is not a proper @JsonDelegate method.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Depth first search for annotated methods in hierarchy.
|
||||
*/
|
||||
public static void findRpcMethods(Class<?> clazz, Set<Class<?>> visited, Consumer<MethodInfo> acceptor) {
|
||||
if (clazz == null || !visited.add(clazz))
|
||||
return;
|
||||
findRpcMethods(clazz.getSuperclass(), visited, acceptor);
|
||||
for (Class<?> interf : clazz.getInterfaces()) {
|
||||
findRpcMethods(interf, visited, acceptor);
|
||||
}
|
||||
String segment = getSegment(clazz);
|
||||
for (Method method : clazz.getDeclaredMethods()) {
|
||||
MethodInfo methodInfo = createMethodInfo(method, segment);
|
||||
if (methodInfo != null) {
|
||||
acceptor.accept(methodInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static String getSegment(Class<?> clazz) {
|
||||
JsonSegment jsonSegment = clazz.getAnnotation(JsonSegment.class);
|
||||
return jsonSegment == null ? "" : jsonSegment.value() + "/";
|
||||
}
|
||||
|
||||
protected static MethodInfo createMethodInfo(Method method, String segment) {
|
||||
if (!method.isSynthetic()) {
|
||||
JsonRequest jsonRequest = method.getAnnotation(JsonRequest.class);
|
||||
if (jsonRequest != null) {
|
||||
return createRequestInfo(method, segment, jsonRequest);
|
||||
}
|
||||
JsonNotification jsonNotification = method.getAnnotation(JsonNotification.class);
|
||||
if (jsonNotification != null) {
|
||||
return createNotificationInfo(method, segment, jsonNotification);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static MethodInfo createNotificationInfo(Method method, String segment, JsonNotification jsonNotification) {
|
||||
MethodInfo methodInfo = createMethodInfo(method, jsonNotification.useSegment(), segment, jsonNotification.value());
|
||||
methodInfo.isNotification = true;
|
||||
return methodInfo;
|
||||
}
|
||||
|
||||
protected static MethodInfo createRequestInfo(Method method, String segment, JsonRequest jsonRequest) {
|
||||
return createMethodInfo(method, jsonRequest.useSegment(), segment, jsonRequest.value());
|
||||
}
|
||||
|
||||
protected static MethodInfo createMethodInfo(Method method, boolean useSegment, String segment, String value) {
|
||||
method.setAccessible(true);
|
||||
|
||||
MethodInfo methodInfo = new MethodInfo();
|
||||
methodInfo.method = method;
|
||||
methodInfo.parameterTypes = getParameterTypes(method);
|
||||
methodInfo.name = getMethodName(method, useSegment, segment, value);
|
||||
return methodInfo;
|
||||
}
|
||||
|
||||
protected static String getMethodName(Method method, boolean useSegment, String segment, String value) {
|
||||
String name = value != null && value.length() > 0 ? value : method.getName();
|
||||
return useSegment ? segment + name : name;
|
||||
}
|
||||
|
||||
protected static Type[] getParameterTypes(Method method) {
|
||||
return Arrays.stream(method.getParameters()).map(t -> t.getParameterizedType()).toArray(Type[]::new);
|
||||
}
|
||||
|
||||
static class MethodInfo {
|
||||
private static Type[] EMPTY_TYPE_ARRAY = {};
|
||||
public String name;
|
||||
public Method method;
|
||||
public Type[] parameterTypes = EMPTY_TYPE_ARRAY;
|
||||
public boolean isNotification = false;
|
||||
}
|
||||
|
||||
static class DelegateInfo {
|
||||
public Method method;
|
||||
public Object delegate;
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.services;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.Endpoint;
|
||||
import org.eclipse.lsp4j.jsonrpc.services.AnnotationUtil.DelegateInfo;
|
||||
import org.eclipse.lsp4j.jsonrpc.services.AnnotationUtil.MethodInfo;
|
||||
|
||||
/**
|
||||
* A Proxy that wraps an {@link Endpoint} in one or more service interfaces, i.e. interfaces
|
||||
* containing {@link JsonNotification} and {@link JsonRequest} methods.
|
||||
*/
|
||||
public class EndpointProxy implements InvocationHandler {
|
||||
|
||||
private final Method object_equals;
|
||||
private final Method object_hashCode;
|
||||
private final Method object_toString;
|
||||
|
||||
private final Endpoint delegate;
|
||||
private final LinkedHashMap<String, MethodInfo> methodInfos;
|
||||
private final LinkedHashMap<String, DelegateInfo> delegatedSegments;
|
||||
|
||||
public EndpointProxy(Endpoint delegate, Class<?> interface_) {
|
||||
this(delegate, Collections.singletonList(interface_));
|
||||
}
|
||||
|
||||
public EndpointProxy(Endpoint delegate, Collection<Class<?>> interfaces) {
|
||||
if (delegate == null)
|
||||
throw new NullPointerException("delegate");
|
||||
if (interfaces == null)
|
||||
throw new NullPointerException("interfaces");
|
||||
if (interfaces.isEmpty())
|
||||
throw new IllegalArgumentException("interfaces must not be empty.");
|
||||
|
||||
this.delegate = delegate;
|
||||
try {
|
||||
object_equals = Object.class.getDeclaredMethod("equals", Object.class);
|
||||
object_hashCode = Object.class.getDeclaredMethod("hashCode");
|
||||
object_toString = Object.class.getDeclaredMethod("toString");
|
||||
} catch (NoSuchMethodException | SecurityException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
methodInfos = new LinkedHashMap<>();
|
||||
delegatedSegments = new LinkedHashMap<>();
|
||||
for (Class<?> interf : interfaces) {
|
||||
AnnotationUtil.findRpcMethods(interf, new HashSet<Class<?>>(), (methodInfo) -> {
|
||||
if (methodInfos.put(methodInfo.method.getName(), methodInfo) != null) {
|
||||
throw new IllegalStateException("Duplicate RPC method " + methodInfo.method);
|
||||
}
|
||||
});
|
||||
AnnotationUtil.findDelegateSegments(interf, new HashSet<Class<?>>(), (method) -> {
|
||||
Object delegateProxy = ServiceEndpoints.toServiceObject(delegate, method.getReturnType());
|
||||
DelegateInfo info = new DelegateInfo();
|
||||
info.delegate = delegateProxy;
|
||||
info.method = method;
|
||||
if (delegatedSegments.put(method.getName(), info) != null) {
|
||||
throw new IllegalStateException("Duplicate RPC method " + method);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
args = args == null ? new Object[0] : args;
|
||||
MethodInfo methodInfo = this.methodInfos.get(method.getName());
|
||||
if (methodInfo != null) {
|
||||
Object params = getParams(args, methodInfo);
|
||||
if (methodInfo.isNotification) {
|
||||
delegate.notify(methodInfo.name, params);
|
||||
return null;
|
||||
}
|
||||
return delegate.request(methodInfo.name, params);
|
||||
}
|
||||
DelegateInfo delegateInfo = this.delegatedSegments.get(method.getName());
|
||||
if (delegateInfo != null) {
|
||||
return delegateInfo.delegate;
|
||||
}
|
||||
if (object_equals.equals(method) && args.length == 1) {
|
||||
if(args[0] != null ) {
|
||||
try {
|
||||
return this.equals(Proxy.getInvocationHandler(args[0]));
|
||||
} catch (IllegalArgumentException exception) {
|
||||
}
|
||||
}
|
||||
return this.equals(args[0]);
|
||||
}
|
||||
if (object_hashCode.equals(method)) {
|
||||
return this.hashCode();
|
||||
}
|
||||
if (object_toString.equals(method)) {
|
||||
return this.toString();
|
||||
}
|
||||
return method.invoke(delegate, args);
|
||||
}
|
||||
|
||||
protected Object getParams(Object[] args, MethodInfo methodInfo) {
|
||||
if (args.length == 0) {
|
||||
return null;
|
||||
}
|
||||
if (args.length == 1) {
|
||||
return args[0];
|
||||
}
|
||||
return Arrays.asList(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + " for " + delegate.toString();
|
||||
}
|
||||
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.services;
|
||||
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.Endpoint;
|
||||
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* An endpoint that reflectively delegates to {@link JsonNotification} and {@link JsonRequest}
|
||||
* methods of one or more given delegate objects.
|
||||
*/
|
||||
public class GenericEndpoint implements Endpoint {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("GenericEndpoint");
|
||||
private static final Object[] NO_ARGUMENTS = {};
|
||||
|
||||
private final LinkedHashMap<String, Function<Object, CompletableFuture<Object>>> methodHandlers =
|
||||
new LinkedHashMap<>();
|
||||
private final List<Object> delegates;
|
||||
|
||||
public GenericEndpoint(Object delegate) {
|
||||
this.delegates = Collections.singletonList(delegate);
|
||||
recursiveFindRpcMethods(delegate, new HashSet<>(), new HashSet<>());
|
||||
}
|
||||
|
||||
public GenericEndpoint(Collection<Object> delegates) {
|
||||
this.delegates = new ArrayList<>(delegates);
|
||||
for (Object delegate : this.delegates) {
|
||||
recursiveFindRpcMethods(delegate, new HashSet<>(), new HashSet<>());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> request(String method, Object parameter) {
|
||||
// Check the registered method handlers
|
||||
Function<Object, CompletableFuture<Object>> handler = methodHandlers.get(method);
|
||||
if (handler != null) {
|
||||
return handler.apply(parameter);
|
||||
}
|
||||
|
||||
// Ask the delegate objects whether they can handle the request generically
|
||||
List<CompletableFuture<?>> futures = new ArrayList<>(delegates.size());
|
||||
for (Object delegate : delegates) {
|
||||
if (delegate instanceof Endpoint) {
|
||||
futures.add(((Endpoint) delegate).request(method, parameter));
|
||||
}
|
||||
}
|
||||
if (!futures.isEmpty()) {
|
||||
return CompletableFuture.anyOf(futures.toArray(new CompletableFuture[futures.size()]));
|
||||
}
|
||||
|
||||
// Create a log message about the unsupported method
|
||||
String message = "Unsupported request method: " + method;
|
||||
if (isOptionalMethod(method)) {
|
||||
LOG.info(message);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
LOG.warn(message);
|
||||
CompletableFuture<?> exceptionalResult = new CompletableFuture<Object>();
|
||||
ResponseError error = new ResponseError(ResponseErrorCode.MethodNotFound, message, null);
|
||||
exceptionalResult.completeExceptionally(new ResponseErrorException(error));
|
||||
return exceptionalResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notify(String method, Object parameter) {
|
||||
// Check the registered method handlers
|
||||
Function<Object, CompletableFuture<Object>> handler = methodHandlers.get(method);
|
||||
if (handler != null) {
|
||||
handler.apply(parameter);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask the delegate objects whether they can handle the notification generically
|
||||
int notifiedDelegates = 0;
|
||||
for (Object delegate : delegates) {
|
||||
if (delegate instanceof Endpoint) {
|
||||
((Endpoint) delegate).notify(method, parameter);
|
||||
notifiedDelegates++;
|
||||
}
|
||||
}
|
||||
|
||||
if (notifiedDelegates == 0) {
|
||||
// Create a log message about the unsupported method
|
||||
String message = "Unsupported notification method: " + method;
|
||||
if (isOptionalMethod(method)) {
|
||||
LOG.info(message);
|
||||
} else {
|
||||
LOG.warn(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isOptionalMethod(String method) {
|
||||
return method != null && method.startsWith("$/");
|
||||
}
|
||||
|
||||
protected void recursiveFindRpcMethods(
|
||||
Object current, Set<Class<?>> visited, Set<Class<?>> visitedForDelegate) {
|
||||
AnnotationUtil.findRpcMethods(
|
||||
current.getClass(),
|
||||
visited,
|
||||
(methodInfo) -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<Object, CompletableFuture<Object>> handler =
|
||||
(arg) -> {
|
||||
try {
|
||||
Method method = methodInfo.method;
|
||||
Object[] arguments = this.getArguments(method, arg);
|
||||
return (CompletableFuture<Object>) method.invoke(current, arguments);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
};
|
||||
if (methodHandlers.put(methodInfo.name, handler) != null) {
|
||||
throw new IllegalStateException("Multiple methods for name " + methodInfo.name);
|
||||
}
|
||||
});
|
||||
AnnotationUtil.findDelegateSegments(
|
||||
current.getClass(),
|
||||
visitedForDelegate,
|
||||
(method) -> {
|
||||
try {
|
||||
Object delegate = method.invoke(current);
|
||||
if (delegate != null) {
|
||||
recursiveFindRpcMethods(delegate, visited, visitedForDelegate);
|
||||
} else {
|
||||
LOG.error(
|
||||
"A delegate object is null, jsonrpc methods of '" + method + "' are ignored");
|
||||
}
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected Object[] getArguments(Method method, Object arg) {
|
||||
int parameterCount = method.getParameterCount();
|
||||
if (parameterCount == 0) {
|
||||
if (arg != null) {
|
||||
LOG.warn("Unexpected params '" + arg + "' for '" + method + "' is ignored");
|
||||
}
|
||||
return NO_ARGUMENTS;
|
||||
}
|
||||
if (arg instanceof List<?>) {
|
||||
List<?> arguments = (List<?>) arg;
|
||||
int argumentCount = arguments.size();
|
||||
if (argumentCount == parameterCount) {
|
||||
return arguments.toArray();
|
||||
}
|
||||
if (argumentCount > parameterCount) {
|
||||
Stream<?> unexpectedArguments = arguments.stream().skip(parameterCount);
|
||||
String unexpectedParams =
|
||||
unexpectedArguments.map(a -> "'" + a + "'").reduce((a, a2) -> a + ", " + a2).get();
|
||||
LOG.warn("Unexpected params " + unexpectedParams + " for '" + method + "' is ignored");
|
||||
return arguments.subList(0, parameterCount).toArray();
|
||||
}
|
||||
return arguments.toArray(new Object[parameterCount]);
|
||||
}
|
||||
Object[] arguments = new Object[parameterCount];
|
||||
arguments[0] = arg;
|
||||
return arguments;
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.services;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* A method annotated with {@link JsonDelegate} is treated as a delegate method.
|
||||
* As a result jsonrpc methods of the delegate will be considered, too.
|
||||
* If an annotated method returns `null`
|
||||
* then jsonrpc methods of the delegate are not considered.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface JsonDelegate {
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016, 2020 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.services;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark a notification method on an interface or class.
|
||||
* <p>
|
||||
* A notification method must be of type <code>void</code> and have zero or one
|
||||
* argument.
|
||||
* <p>
|
||||
* According to jsonrpc an argument must be an 'object' (a java bean, not e,g.
|
||||
* String).
|
||||
* <p>
|
||||
* The name of the jsonrpc notification will be the optional segment, followed
|
||||
* by the name of the Java method that is annotated with JsonNotification. The
|
||||
* name of the jsonrpc notification can be customized by using the
|
||||
* {@link #value()} field of this annotation. To specify the whole name,
|
||||
* including the segment, in the value, set {@link #useSegment()} to false.
|
||||
*
|
||||
* @see JsonSegment
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface JsonNotification {
|
||||
/**
|
||||
* The name of the the jsonrpc request method. If empty, uses the name of the
|
||||
* annotated method.
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* When using segments, useSegment will be true to prepend the segment name to
|
||||
* the name of the request.
|
||||
*
|
||||
* @see JsonSegment
|
||||
*/
|
||||
boolean useSegment() default true;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016, 2020 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.services;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Annotation to mark a request method on an interface or class.
|
||||
* <p>
|
||||
* A request method must have the return type {@link CompletableFuture} with an
|
||||
* object parameter type or Void and have zero or one argument.
|
||||
* <p>
|
||||
* According to jsonrpc an argument must be an 'object' (a java bean, not e,g.
|
||||
* String).
|
||||
* <p>
|
||||
* The name of the jsonrpc request will be the optional segment, followed by the
|
||||
* name of the Java method that is annotated with JsonRequest. The name of the
|
||||
* jsonrpc request can be customized by using the {@link #value()} field of this
|
||||
* annotation. To specify the whole name, including the segment, in the value,
|
||||
* set {@link #useSegment()} to false.
|
||||
*
|
||||
* @see JsonSegment
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
public @interface JsonRequest {
|
||||
/**
|
||||
* The name of the the jsonrpc request method. If empty, uses the name of the
|
||||
* annotated method.
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* When using segments, useSegment will be true to prepend the segment name to
|
||||
* the name of the request.
|
||||
*
|
||||
* @see JsonSegment
|
||||
*/
|
||||
boolean useSegment() default true;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.services;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Use on a class or interface to prefix all declared {@link JsonRequest} and {@link JsonNotification} methods.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface JsonSegment {
|
||||
String value() default "";
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.services;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.Endpoint;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.JsonRpcMethod;
|
||||
import org.eclipse.lsp4j.jsonrpc.json.ResponseJsonAdapter;
|
||||
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
|
||||
public final class ServiceEndpoints {
|
||||
private ServiceEndpoints() {}
|
||||
|
||||
/**
|
||||
* Wraps a given {@link Endpoint} in the given service interface.
|
||||
*
|
||||
* @return the wrapped service object
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T toServiceObject(Endpoint endpoint, Class<T> interface_) {
|
||||
Class<?>[] interfArray = new Class[]{interface_, Endpoint.class};
|
||||
EndpointProxy invocationHandler = new EndpointProxy(endpoint, interface_);
|
||||
return (T) Proxy.newProxyInstance(interface_.getClassLoader(), interfArray, invocationHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a given {@link Endpoint} in the given service interfaces.
|
||||
*
|
||||
* @return the wrapped service object
|
||||
*/
|
||||
public static Object toServiceObject(Endpoint endpoint, Collection<Class<?>> interfaces, ClassLoader classLoader) {
|
||||
Class<?>[] interfArray = new Class[interfaces.size() + 1];
|
||||
interfaces.toArray(interfArray);
|
||||
interfArray[interfArray.length - 1] = Endpoint.class;
|
||||
EndpointProxy invocationHandler = new EndpointProxy(endpoint, interfaces);
|
||||
return Proxy.newProxyInstance(classLoader, interfArray, invocationHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a given object with service annotations behind an {@link Endpoint} interface.
|
||||
*
|
||||
* @return the wrapped service endpoint
|
||||
*/
|
||||
public static Endpoint toEndpoint(Object serviceObject) {
|
||||
return new GenericEndpoint(serviceObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a collection of objects with service annotations behind an {@link Endpoint} interface.
|
||||
*
|
||||
* @return the wrapped service endpoint
|
||||
*/
|
||||
public static Endpoint toEndpoint(Collection<Object> serviceObjects) {
|
||||
return new GenericEndpoint(serviceObjects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all Json RPC methods on a given class.
|
||||
*
|
||||
* @return the supported JsonRpcMethods
|
||||
*/
|
||||
public static Map<String, JsonRpcMethod> getSupportedMethods(Class<?> type) {
|
||||
Set<Class<?>> visitedTypes = new HashSet<>();
|
||||
return getSupportedMethods(type, visitedTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all Json RPC methods on a given type
|
||||
*/
|
||||
private static Map<String, JsonRpcMethod> getSupportedMethods(Class<?> type, Set<Class<?>> visitedTypes) {
|
||||
Map<String, JsonRpcMethod> result = new LinkedHashMap<String, JsonRpcMethod>();
|
||||
AnnotationUtil.findRpcMethods(type, visitedTypes, (methodInfo) -> {
|
||||
JsonRpcMethod meth;
|
||||
if (methodInfo.isNotification) {
|
||||
meth = JsonRpcMethod.notification(methodInfo.name, methodInfo.parameterTypes);
|
||||
} else {
|
||||
Type genericReturnType = methodInfo.method.getGenericReturnType();
|
||||
if (genericReturnType instanceof ParameterizedType) {
|
||||
Type returnType = ((ParameterizedType) genericReturnType).getActualTypeArguments()[0];
|
||||
TypeAdapterFactory responseTypeAdapter = null;
|
||||
ResponseJsonAdapter responseTypeAdapterAnnotation = methodInfo.method.getAnnotation(ResponseJsonAdapter.class);
|
||||
if (responseTypeAdapterAnnotation != null) {
|
||||
try {
|
||||
responseTypeAdapter = responseTypeAdapterAnnotation.value().newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
meth = JsonRpcMethod.request(methodInfo.name, returnType, responseTypeAdapter, methodInfo.parameterTypes);
|
||||
} else {
|
||||
throw new IllegalStateException("Expecting return type of CompletableFuture but was : " + genericReturnType);
|
||||
}
|
||||
}
|
||||
if (result.put(methodInfo.name, meth) != null) {
|
||||
throw new IllegalStateException("Duplicate RPC method "+methodInfo.name+".");
|
||||
};
|
||||
});
|
||||
|
||||
AnnotationUtil.findDelegateSegments(type, new HashSet<>(), (method)-> {
|
||||
Map<String, JsonRpcMethod> supportedDelegateMethods = getSupportedMethods(method.getReturnType(), visitedTypes);
|
||||
for (JsonRpcMethod meth : supportedDelegateMethods.values()) {
|
||||
if (result.put(meth.getMethodName(), meth) != null) {
|
||||
throw new IllegalStateException("Duplicate RPC method "+meth.getMethodName()+".");
|
||||
};
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.validation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates that the annotated field or parameter must not be set to {@code null}. If validation
|
||||
* is active, an exception is thrown when a message is received where a {@code NonNull} field
|
||||
* has a {@code null} or {@code undefined} value.
|
||||
*
|
||||
* In order to achieve consistent behavior, for every field with this annotation the corresponding
|
||||
* getter method as well as the parameter of the corresponding setter method are also expected to
|
||||
* be annotated.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE_PARAMETER})
|
||||
public @interface NonNull {
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2016 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0,
|
||||
* or the Eclipse Distribution License v. 1.0 which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
|
||||
******************************************************************************/
|
||||
package org.eclipse.lsp4j.jsonrpc.validation;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
|
||||
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
|
||||
import org.eclipse.lsp4j.jsonrpc.MessageIssueException;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Message;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.MessageIssue;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
/**
|
||||
* Validates messages and forwards them to other message consumers. In case an issue is found, a
|
||||
* {@link MessageIssueException} is thrown.
|
||||
*/
|
||||
public class ReflectiveMessageValidator implements MessageConsumer {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("ReflectiveMessageValidator");
|
||||
|
||||
private final MessageConsumer delegate;
|
||||
|
||||
/** When created with this constructor, the validator acts as a message sink. */
|
||||
public ReflectiveMessageValidator() {
|
||||
this.delegate = null;
|
||||
}
|
||||
|
||||
/** Forward messages to the given consumer unless an issue is found. */
|
||||
public ReflectiveMessageValidator(MessageConsumer delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(Message message) throws MessageIssueException, JsonRpcException {
|
||||
List<MessageIssue> issues = validate(message);
|
||||
if (!issues.isEmpty()) {
|
||||
// Sort the messages in order to get a stable order (otherwise it depends on the JVM's
|
||||
// reflection implementation)
|
||||
Collections.sort(issues, (issue1, issue2) -> issue1.getText().compareTo(issue2.getText()));
|
||||
throw new MessageIssueException(message, issues);
|
||||
} else if (delegate != null) {
|
||||
delegate.consume(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Check whether the given object is valid. If it is not valid, its issues are not reported. */
|
||||
public boolean isValid(Object object) {
|
||||
List<MessageIssue> issues = validate(object);
|
||||
return issues.isEmpty();
|
||||
}
|
||||
|
||||
protected List<MessageIssue> validate(Object object) {
|
||||
List<MessageIssue> result = new ArrayList<>();
|
||||
try {
|
||||
validate(object, result, new LinkedList<>(), new LinkedList<>());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error during message validation: " + e.getMessage(), e);
|
||||
result.add(
|
||||
new MessageIssue(
|
||||
"Message validation failed, please check the logs of the remote endpoint.",
|
||||
ResponseErrorCode.InvalidParams.getValue()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Validate all fields of the given object. */
|
||||
protected void validate(
|
||||
Object object,
|
||||
List<MessageIssue> issues,
|
||||
Deque<Object> objectStack,
|
||||
Deque<Object> accessorStack)
|
||||
throws Exception {
|
||||
if (object == null
|
||||
|| object instanceof Enum<?>
|
||||
|| object instanceof String
|
||||
|| object instanceof Number
|
||||
|| object instanceof Boolean
|
||||
|| object instanceof JsonElement
|
||||
|| object instanceof Throwable) {
|
||||
return;
|
||||
}
|
||||
if (objectStack.contains(object)) {
|
||||
issues.add(
|
||||
new MessageIssue(
|
||||
"An element of the message has a direct or indirect reference to itself."
|
||||
+ " Path: "
|
||||
+ createPathString(accessorStack),
|
||||
ResponseErrorCode.InvalidParams.getValue()));
|
||||
return;
|
||||
}
|
||||
objectStack.push(object);
|
||||
if (object instanceof List<?>) {
|
||||
ListIterator<?> iter = ((List<?>) object).listIterator();
|
||||
while (iter.hasNext()) {
|
||||
accessorStack.push(iter.nextIndex());
|
||||
Object element = iter.next();
|
||||
if (element == null) {
|
||||
issues.add(
|
||||
new MessageIssue(
|
||||
"Lists must not contain null references."
|
||||
+ " Path: "
|
||||
+ createPathString(accessorStack),
|
||||
ResponseErrorCode.InvalidParams.getValue()));
|
||||
}
|
||||
validate(element, issues, objectStack, accessorStack);
|
||||
accessorStack.pop();
|
||||
}
|
||||
} else if (object instanceof Either<?, ?>) {
|
||||
Either<?, ?> either = (Either<?, ?>) object;
|
||||
if (either.isLeft()) {
|
||||
validate(either.getLeft(), issues, objectStack, accessorStack);
|
||||
} else if (either.isRight()) {
|
||||
validate(either.getRight(), issues, objectStack, accessorStack);
|
||||
} else {
|
||||
issues.add(
|
||||
new MessageIssue(
|
||||
"An Either instance must not be empty."
|
||||
+ " Path: "
|
||||
+ createPathString(accessorStack),
|
||||
ResponseErrorCode.InvalidParams.getValue()));
|
||||
}
|
||||
} else {
|
||||
for (Method method : object.getClass().getMethods()) {
|
||||
if (isGetter(method)) {
|
||||
accessorStack.push(method);
|
||||
Object value = method.invoke(object);
|
||||
if (value == null && method.getAnnotation(NonNull.class) != null) {
|
||||
issues.add(
|
||||
new MessageIssue(
|
||||
"The accessor '"
|
||||
+ method.getDeclaringClass().getSimpleName()
|
||||
+ "."
|
||||
+ method.getName()
|
||||
+ "()' must return a non-null value."
|
||||
+ " Path: "
|
||||
+ createPathString(accessorStack),
|
||||
ResponseErrorCode.InvalidParams.getValue()));
|
||||
}
|
||||
validate(value, issues, objectStack, accessorStack);
|
||||
accessorStack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
objectStack.pop();
|
||||
}
|
||||
|
||||
protected String createPathString(Deque<Object> accessorStack) {
|
||||
StringBuilder result = new StringBuilder("$");
|
||||
Iterator<Object> resultIter = accessorStack.descendingIterator();
|
||||
while (resultIter.hasNext()) {
|
||||
Object accessor = resultIter.next();
|
||||
if (accessor instanceof Method) result.append('.').append(getPropertyName((Method) accessor));
|
||||
else if (accessor instanceof Integer) result.append('[').append(accessor).append(']');
|
||||
else result.append(accessor);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
protected boolean isGetter(Method method) {
|
||||
return method.getParameterCount() == 0
|
||||
&& method.getName().startsWith("get")
|
||||
&& method.getDeclaringClass() != Object.class
|
||||
&& Modifier.isPublic(method.getModifiers())
|
||||
&& !Modifier.isStatic(method.getModifiers());
|
||||
}
|
||||
|
||||
protected String getPropertyName(Method method) {
|
||||
String methodName = method.getName();
|
||||
if (methodName.startsWith("get") && methodName.length() > 3)
|
||||
return methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
|
||||
else return methodName;
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ java {
|
||||
|
||||
dependencies {
|
||||
implementation project (path: ':build-tools:logger')
|
||||
api project (path: ':build-tools:jsonrpc')
|
||||
api project (path: ':build-tools:tooling-api-model')
|
||||
api project (path: ':build-tools:tooling-api-events')
|
||||
|
||||
@@ -34,8 +35,6 @@ dependencies {
|
||||
|
||||
testImplementation tests.junit
|
||||
testImplementation tests.google_truth
|
||||
|
||||
api common.jsonrpc
|
||||
api google.gson
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ dependencies {
|
||||
api files ('libs/org.eclipse.lemminx-0.21.0.jar')
|
||||
api "org.eclipse.lsp4j:org.eclipse.lsp4j:0.12.0"
|
||||
|
||||
implementation common.jsonrpc
|
||||
implementation "com.google.guava:guava:31.1-jre"
|
||||
implementation "com.google.code.gson:gson:2.8.9"
|
||||
implementation "xerces:xercesImpl:2.12.2"
|
||||
|
||||
@@ -28,3 +28,4 @@ include ':build-tools:xml-dom'
|
||||
include ':build-tools:xml-formatter'
|
||||
include ':build-tools:fuzzysearch'
|
||||
include ':build-tools:builder-model-impl'
|
||||
include ':build-tools:jsonrpc'
|
||||
|
||||
Reference in New Issue
Block a user