├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── com │ └── neovim │ ├── Buffer.java │ ├── Dispatcher.java │ ├── DispatcherHelper.java │ ├── EmbeddedNeovim.java │ ├── Neovim.java │ ├── NeovimHandler.java │ ├── NeovimModule.java │ ├── Position.java │ ├── SocketNeovim.java │ ├── TabPage.java │ ├── Window.java │ └── msgpack │ ├── IOBiFunction.java │ ├── JsonNodeUtil.java │ ├── MessagePackRPC.java │ ├── NeovimException.java │ ├── Notification.java │ ├── Packet.java │ ├── Request.java │ ├── RequestCallback.java │ ├── RequestIdGenerator.java │ └── Response.java └── test └── java └── com └── neovim ├── BufferTest.java ├── DispatcherTest.java ├── TabPageTest.java ├── WindowTest.java └── msgpack ├── MessagePackRPCTest.java ├── NeovimExceptionTest.java ├── NotificationTest.java ├── RequestCallbackTest.java ├── RequestIdGeneratorTest.java ├── RequestTest.java └── ResponseTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /msgpack-java-046c5d5cdd.pom 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | 5 | sudo: false 6 | cache: 7 | directories: 8 | - $HOME/.m2 9 | before_cache: 10 | - mvn build-helper:remove-project-artifact 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Frank Dinoff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Neovim Java Client 2 | 3 | [![Join the chat at https://gitter.im/fdinoff/neovim-java-client](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/fdinoff/neovim-java-client?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | [![Build Status](https://travis-ci.org/fdinoff/neovim-java-client.svg?branch=master)](https://travis-ci.org/fdinoff/neovim-java-client) 5 | 6 | Wrapper around the neovim message pack api to talk to a neovim process. 7 | This is a WIP lots of functions do not have wrappers yet. 8 | 9 | ## Usage 10 | 11 | Standard usage would be to create a connection to a neovim instance. 12 | There are two provided connection types. 13 | 14 | - SocketNeovim 15 | - Connection to a socket of the form address:port. 16 | - This is **NOT** a connection to a UNIX socket. 17 | ```java 18 | MessagePackRPC.Connection connection = new SocketNeovim("127.0.0.1:6666"); 19 | ``` 20 | - EmbeddedNeovim 21 | - Connection to an embedded neovim launched with the --embed flag 22 | ```java 23 | MessagePackRPC.Connection connection = new EmbeddedNeovim("nvim"); 24 | ``` 25 | 26 | Once you have a connection you can create a Neovim instance that will talk to the connected neovim instance. 27 | 28 | ```java 29 | try (Neovim neovim = Neovim.connectTo(connection)) { 30 | 31 | } 32 | ``` 33 | 34 | ## Notes 35 | 36 | UNIX domain sockets are not supported out of the box by this library. 37 | These sockets are not supported by the SDK and require JNI code to use. 38 | There are libraries that provide this support and you can wrap the resulting socket in a `MessagePackRPC.Connection` 39 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.neovim 8 | neovim-java-client 9 | 0.0-SNAPSHOT 10 | 11 | 12 | UTF-8 13 | 14 | 15 | 16 | 17 | org.msgpack 18 | msgpack-core 19 | 0.7.0-M6 20 | 21 | 22 | org.msgpack 23 | jackson-dataformat-msgpack 24 | 0.7.0-M6 25 | 26 | 27 | org.slf4j 28 | slf4j-api 29 | 1.7.12 30 | 31 | 32 | org.slf4j 33 | slf4j-jdk14 34 | 1.7.12 35 | test 36 | 37 | 38 | com.google.guava 39 | guava 40 | 18.0 41 | 42 | 43 | junit 44 | junit 45 | 4.11 46 | test 47 | 48 | 49 | org.hamcrest 50 | hamcrest-all 51 | 1.3 52 | test 53 | 54 | 55 | org.mockito 56 | mockito-all 57 | 1.10.19 58 | test 59 | 60 | 61 | 62 | 63 | 64 | org.apache.maven.plugins 65 | maven-compiler-plugin 66 | 2.3.2 67 | 68 | 1.8 69 | 1.8 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/Buffer.java: -------------------------------------------------------------------------------- 1 | package com.neovim; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.google.common.base.MoreObjects; 5 | import com.google.common.base.Objects; 6 | import com.neovim.msgpack.MessagePackRPC; 7 | 8 | import java.util.List; 9 | import java.util.concurrent.CompletableFuture; 10 | 11 | import static com.google.common.base.Preconditions.checkNotNull; 12 | 13 | public class Buffer { 14 | private final MessagePackRPC messagePackRPC; 15 | private final long id; 16 | 17 | Buffer(MessagePackRPC messagePackRPC, long id) { 18 | this.messagePackRPC = checkNotNull(messagePackRPC); 19 | this.id = id; 20 | } 21 | 22 | long getId() { 23 | return id; 24 | } 25 | 26 | public CompletableFuture getLineCount() { 27 | return messagePackRPC.sendRequest(Long.class, "buffer_line_count", this); 28 | } 29 | 30 | public CompletableFuture getLine(int index) { 31 | return messagePackRPC.sendRequest(byte[].class, "buffer_get_line", this, index); 32 | } 33 | 34 | public void setLine(int index, byte[] line) { 35 | messagePackRPC.sendNotification("buffer_set_line", this, index, line); 36 | } 37 | 38 | public void deleteLine(int index) { 39 | messagePackRPC.sendNotification("buffer_del_line", this, index); 40 | } 41 | 42 | public CompletableFuture> getLineSlice( 43 | long start, long end, boolean includeStart, boolean includeEnd) { 44 | return messagePackRPC.sendRequest( 45 | new TypeReference>() {}, 46 | "buffer_get_line_slice", 47 | this, 48 | start, 49 | end, 50 | includeStart, 51 | includeEnd); 52 | } 53 | 54 | public void setLineSlice( 55 | long start, 56 | long end, 57 | boolean includeStart, 58 | boolean includeEnd, 59 | List replacements) { 60 | messagePackRPC.sendNotification( 61 | "buffer_set_line_slice", this, start, end, includeStart, includeEnd, replacements); 62 | } 63 | 64 | public CompletableFuture getVar(TypeReference type, String name) { 65 | return messagePackRPC.sendRequest(type, "buffer_get_var", this, name); 66 | } 67 | 68 | public CompletableFuture setVar(TypeReference type, String name, T value) { 69 | return messagePackRPC.sendRequest(type, "buffer_set_var", this, name, value); 70 | } 71 | 72 | public CompletableFuture getOption(TypeReference type, String name) { 73 | return messagePackRPC.sendRequest(type, "buffer_get_option", this, name); 74 | } 75 | 76 | public CompletableFuture setOption(TypeReference type, String name, T value) { 77 | return messagePackRPC.sendRequest(type, "buffer_set_option", this, name, value); 78 | } 79 | 80 | public CompletableFuture getBufferNumber() { 81 | return messagePackRPC.sendRequest(Long.class, "buffer_get_number", this); 82 | } 83 | 84 | public CompletableFuture getName() { 85 | return messagePackRPC.sendRequest(byte[].class, "buffer_get_name", this); 86 | } 87 | 88 | public void setName(String name) { 89 | messagePackRPC.sendNotification("buffer_set_name", this, name); 90 | } 91 | 92 | public CompletableFuture isValid() { 93 | return messagePackRPC.sendRequest(Boolean.class, "buffer_is_valid", this); 94 | } 95 | 96 | public void insert(int lineNumber, List lines) { 97 | messagePackRPC.sendNotification("buffer_insert", this, lineNumber, lines); 98 | } 99 | 100 | public CompletableFuture getMark(String name) { 101 | return messagePackRPC.sendRequest(Position.class, "buffer_get_mark", this, name); 102 | } 103 | 104 | /** 105 | * Deletes the corresponding buffer in vim. 106 | */ 107 | public void bdelete() { 108 | getBufferNumber().thenAccept( 109 | num -> messagePackRPC.sendNotification("vim_command", "bdelete " + num)); 110 | } 111 | 112 | @Override 113 | public boolean equals(Object o) { 114 | if (this == o) return true; 115 | if (o == null || getClass() != o.getClass()) return false; 116 | 117 | Buffer that = (Buffer) o; 118 | 119 | return Objects.equal(this.messagePackRPC, that.messagePackRPC) && 120 | Objects.equal(this.id, that.id); 121 | } 122 | 123 | @Override 124 | public int hashCode() { 125 | return Objects.hashCode(messagePackRPC, id); 126 | } 127 | 128 | @Override 129 | public String toString() { 130 | return MoreObjects.toStringHelper(this) 131 | .add("messagePackRPC", messagePackRPC) 132 | .add("id", id) 133 | .toString(); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/Dispatcher.java: -------------------------------------------------------------------------------- 1 | package com.neovim; 2 | 3 | import com.fasterxml.jackson.databind.JavaType; 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.databind.node.ArrayNode; 7 | import com.neovim.msgpack.NeovimException; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.io.IOException; 12 | import java.lang.reflect.InvocationTargetException; 13 | import java.lang.reflect.Method; 14 | import java.lang.reflect.Type; 15 | import java.util.concurrent.ConcurrentHashMap; 16 | import java.util.concurrent.ConcurrentMap; 17 | 18 | import static com.google.common.base.Preconditions.checkArgument; 19 | import static com.google.common.base.Preconditions.checkNotNull; 20 | import static com.google.common.base.Preconditions.checkState; 21 | import static com.google.common.base.Throwables.getRootCause; 22 | 23 | public class Dispatcher { 24 | private static final Logger log = LoggerFactory.getLogger(Dispatcher.class); 25 | 26 | private final ConcurrentMap handlers = new ConcurrentHashMap<>(); 27 | private final ObjectMapper objectMapper; 28 | 29 | public Dispatcher(ObjectMapper objectMapper) { 30 | this.objectMapper = checkNotNull(objectMapper); 31 | } 32 | 33 | public void register(Object handler) { 34 | for (Method method : handler.getClass().getMethods()) { 35 | NeovimHandler neovimHandler = method.getAnnotation(NeovimHandler.class); 36 | if (neovimHandler != null) { 37 | String name = neovimHandler.value(); 38 | 39 | checkState(!handlers.containsKey(name), 40 | "Already registered request handler with name %s", name); 41 | handlers.put(neovimHandler.value(), new Invoker(handler, method)); 42 | } 43 | } 44 | if (handler instanceof DispatcherHelper) { 45 | ((DispatcherHelper) handler).setDispatcher(this); 46 | } 47 | } 48 | 49 | public Object dispatchMethod(String name, JsonNode object) { 50 | Invoker method = handlers.get(name); 51 | if (method == null) { 52 | log.warn("Received notification {}({})", name, object); 53 | return new NeovimException(0, "No such method: " + name); 54 | } 55 | 56 | try { 57 | return method.invoke(object); 58 | } catch (Exception e) { 59 | log.error("{}", e.getMessage(), e); 60 | return new NeovimException(0, getRootCause(e).getMessage()); 61 | } 62 | } 63 | 64 | private class Invoker { 65 | private final Object object; 66 | private final Method method; 67 | 68 | public Invoker(Object object, Method method) { 69 | this.object = checkNotNull(object); 70 | this.method = checkNotNull(method); 71 | } 72 | 73 | public Object invoke(JsonNode nodes) throws 74 | InvocationTargetException, IllegalAccessException, IOException { 75 | checkArgument(nodes.isArray(), "Argument is supposed to be an array"); 76 | Type[] types = method.getGenericParameterTypes(); 77 | 78 | Object[] args = new Object[types.length]; 79 | 80 | if (types.length == 1 && types[0] == JsonNode.class) { 81 | // Pass nodes directly 82 | args[0] = nodes; 83 | } else if (method.isVarArgs() && types.length <= nodes.size()) { 84 | // Handle java var args method 85 | int i; 86 | for (i = 0; i < types.length - 1; i++) { 87 | args[i] = objectMapper.readValue( 88 | nodes.get(i).traverse(), 89 | objectMapper.constructType(types[i])); 90 | } 91 | 92 | ArrayNode arrayNode = objectMapper.createArrayNode(); 93 | for (; i < nodes.size(); i++) { 94 | arrayNode.add(nodes.get(i)); 95 | } 96 | 97 | args[types.length - 1] = objectMapper.readValue( 98 | arrayNode.traverse(), objectMapper.constructType(types[types.length - 1])); 99 | } else if (types.length == nodes.size()) { 100 | // Each element in the array is an argument 101 | for (int i = 0; i < types.length; i++) { 102 | args[i] = objectMapper.readValue( 103 | nodes.get(i).traverse(), 104 | objectMapper.constructType(types[i])); 105 | } 106 | } else if (types.length == 1) { 107 | // The array is the argument 108 | JavaType javaType = objectMapper.constructType(types[0]); 109 | args[0] = objectMapper.readValue(nodes.traverse(), javaType); 110 | } else { 111 | throw new IllegalArgumentException("Can't convert arguments"); 112 | } 113 | return method.invoke(object, args); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/DispatcherHelper.java: -------------------------------------------------------------------------------- 1 | package com.neovim; 2 | 3 | public interface DispatcherHelper { 4 | void setDispatcher(Dispatcher dispatcher); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/EmbeddedNeovim.java: -------------------------------------------------------------------------------- 1 | package com.neovim; 2 | 3 | import com.neovim.msgpack.MessagePackRPC; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.InputStreamReader; 11 | import java.io.OutputStream; 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | import static com.google.common.base.Preconditions.checkNotNull; 18 | 19 | public class EmbeddedNeovim implements MessagePackRPC.Connection { 20 | private final Logger log = LoggerFactory.getLogger(getClass()); 21 | private final Thread thread; 22 | private Process neovim; 23 | 24 | private static List createArgs(String executable, String[] args) { 25 | List allArgs = new ArrayList<>(2 + args.length); 26 | allArgs.add(executable); 27 | allArgs.add("--embed"); 28 | allArgs.addAll(Arrays.asList(args)); 29 | return allArgs; 30 | } 31 | 32 | public EmbeddedNeovim(String executable, String... args) throws IOException { 33 | ProcessBuilder pb = new ProcessBuilder(createArgs(executable, args)); 34 | neovim = pb.start(); 35 | thread = new Thread(new StreamLogger(log, neovim.getErrorStream())); 36 | thread.setDaemon(true); 37 | thread.start(); 38 | } 39 | 40 | @Override 41 | public OutputStream getOutputStream() { 42 | return neovim.getOutputStream(); 43 | } 44 | 45 | @Override 46 | public InputStream getInputStream() { 47 | return neovim.getInputStream(); 48 | } 49 | 50 | @Override 51 | public void close() throws IOException { 52 | try { 53 | if (neovim.waitFor(60, TimeUnit.SECONDS)) { 54 | log.info("neovim exited with {}", neovim.exitValue()); 55 | thread.join(); 56 | } else { 57 | neovim.destroy(); 58 | log.info("neovim exited with {}", neovim.waitFor()); 59 | } 60 | } catch (InterruptedException e) { 61 | Thread.currentThread().interrupt(); 62 | } 63 | } 64 | 65 | private static class StreamLogger implements Runnable { 66 | private final InputStream stream; 67 | private final Logger log; 68 | 69 | public StreamLogger(Logger log, InputStream stream) { 70 | this.log = checkNotNull(log); 71 | this.stream = checkNotNull(stream); 72 | } 73 | 74 | @Override 75 | public void run() { 76 | try (InputStreamReader input = new InputStreamReader(stream); 77 | BufferedReader reader = new BufferedReader(input)) { 78 | String line; 79 | while ((line = reader.readLine()) != null) { 80 | log.error("{}", line); 81 | } 82 | } catch (IOException ignored) { 83 | // The stream will always be closed and once the stream is exhausted there is 84 | // nothing left to be printed. 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/Neovim.java: -------------------------------------------------------------------------------- 1 | package com.neovim; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.neovim.msgpack.MessagePackRPC; 6 | 7 | import java.io.IOException; 8 | import java.nio.charset.Charset; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.List; 11 | import java.util.concurrent.CompletableFuture; 12 | 13 | import static com.google.common.base.Preconditions.checkNotNull; 14 | 15 | public class Neovim implements AutoCloseable { 16 | 17 | private final MessagePackRPC messagePackRPC; 18 | private final Dispatcher dispatcher; 19 | 20 | public static Neovim connectTo(MessagePackRPC.Connection connection, Object... handlers) { 21 | ObjectMapper objectMapper = MessagePackRPC.defaultObjectMapper(); 22 | MessagePackRPC messagePackRPC = new MessagePackRPC(connection, objectMapper); 23 | 24 | Dispatcher dispatcher = new Dispatcher(objectMapper); 25 | messagePackRPC.setNotificationHandler(dispatcher::dispatchMethod); 26 | messagePackRPC.setRequestHandler(dispatcher::dispatchMethod); 27 | for (Object handler : handlers) { 28 | dispatcher.register(handler); 29 | } 30 | 31 | Neovim neovim = new Neovim(messagePackRPC, dispatcher); 32 | messagePackRPC.registerModule(new NeovimModule(messagePackRPC)); 33 | messagePackRPC.start(); 34 | return neovim; 35 | } 36 | 37 | public void register(Object handler) { 38 | dispatcher.register(handler); 39 | } 40 | 41 | Neovim(MessagePackRPC messagePackRPC, Dispatcher dispatcher) { 42 | this.messagePackRPC = checkNotNull(messagePackRPC); 43 | this.dispatcher = checkNotNull(dispatcher); 44 | } 45 | 46 | public CompletableFuture getEncoding() { 47 | return getOption(byte[].class, "encoding").thenApply( 48 | bytes -> Charset.forName(new String(bytes, StandardCharsets.US_ASCII))); 49 | } 50 | 51 | public void sendVimCommand(String command) { 52 | messagePackRPC.sendNotification("vim_command", command); 53 | } 54 | 55 | public void feedKeys(String keys, String mode, boolean escapeCsi) { 56 | messagePackRPC.sendNotification("vim_feedkeys", keys, mode, escapeCsi); 57 | } 58 | 59 | public CompletableFuture sendInput(String input) { 60 | return messagePackRPC.sendRequest(Long.class, "vim_input", input); 61 | } 62 | 63 | public CompletableFuture replaceTermcodes( 64 | String str, boolean fromPart, boolean doLt, boolean special) { 65 | return messagePackRPC.sendRequest( 66 | String.class, "vim_replace_termcodes", str, fromPart, doLt, special); 67 | } 68 | 69 | public CompletableFuture commandOutput(String str) { 70 | return messagePackRPC.sendRequest(String.class, "vim_command_output", str); 71 | } 72 | 73 | public CompletableFuture eval(TypeReference type, String str) { 74 | return messagePackRPC.sendRequest(type, "vim_eval", str); 75 | } 76 | 77 | public CompletableFuture stringWidth(String str) { 78 | return messagePackRPC.sendRequest(Long.class, "vim_strwidth", str); 79 | } 80 | 81 | public CompletableFuture> getRuntimePaths() { 82 | return messagePackRPC.sendRequest( 83 | new TypeReference>() {}, "vim_list_runtime_paths"); 84 | } 85 | 86 | public void changeDirectory(String directory) { 87 | messagePackRPC.sendNotification("vim_change_directory", directory); 88 | } 89 | 90 | public CompletableFuture getCurrentLine() { 91 | return messagePackRPC.sendRequest(byte[].class, "vim_get_current_line"); 92 | } 93 | 94 | public void setCurrentLine(byte[] line) { 95 | messagePackRPC.sendNotification("vim_set_current_line", line); 96 | } 97 | 98 | public void deleteCurrentLine() { 99 | messagePackRPC.sendNotification("vim_del_current_line"); 100 | } 101 | 102 | public CompletableFuture getVar(TypeReference type, String name) { 103 | return messagePackRPC.sendRequest(type, "vim_get_var", name); 104 | } 105 | 106 | public CompletableFuture setVar(TypeReference type, String name, T value) { 107 | return messagePackRPC.sendRequest(type, "vim_set_var", name, value); 108 | } 109 | 110 | public CompletableFuture getInternalVar(TypeReference type, String name) { 111 | return messagePackRPC.sendRequest(type, "vim_get_vvar", name); 112 | } 113 | 114 | public CompletableFuture getOption(Class type, String str) { 115 | return messagePackRPC.sendRequest(type, "vim_get_option", str); 116 | } 117 | 118 | public void setOption(String str, T value) { 119 | messagePackRPC.sendNotification("vim_set_option", str, value); 120 | } 121 | 122 | public void writeOutput(String str) { 123 | messagePackRPC.sendNotification("vim_out_write", str); 124 | } 125 | 126 | public void writeError(String str) { 127 | messagePackRPC.sendNotification("vim_err_write", str); 128 | } 129 | 130 | public void reportError(String str) { 131 | messagePackRPC.sendNotification("vim_report_error", str); 132 | } 133 | 134 | public CompletableFuture> getBuffers() { 135 | return messagePackRPC.sendRequest(new TypeReference>() {}, "vim_get_buffers"); 136 | } 137 | 138 | public CompletableFuture getCurrentBuffer() { 139 | return messagePackRPC.sendRequest(new TypeReference() {}, "vim_get_current_buffer"); 140 | } 141 | 142 | public void setCurrentBuffer(Buffer buf) { 143 | messagePackRPC.sendNotification("vim_set_current_buffer", buf); 144 | } 145 | 146 | public CompletableFuture> getWindows() { 147 | return messagePackRPC.sendRequest(new TypeReference>() {}, "vim_get_windows"); 148 | } 149 | 150 | public CompletableFuture getCurrentWindow() { 151 | return messagePackRPC.sendRequest(Window.class, "vim_get_current_window"); 152 | } 153 | 154 | public void setCurrentWindow(Window window) { 155 | messagePackRPC.sendNotification("vim_set_current_window", window); 156 | } 157 | 158 | public CompletableFuture> getTabPages() { 159 | return messagePackRPC.sendRequest( 160 | new TypeReference>() {}, "vim_get_tabpages"); 161 | } 162 | 163 | public CompletableFuture getCurrentTabPage() { 164 | return messagePackRPC.sendRequest(TabPage.class, "vim_get_current_tabpage"); 165 | } 166 | 167 | public void setCurrentTabPage(TabPage tabPage) { 168 | messagePackRPC.sendNotification("vim_set_current_tabpage", tabPage); 169 | } 170 | 171 | public void subscribe(String event) { 172 | messagePackRPC.sendNotification("vim_subscribe", event); 173 | } 174 | 175 | public void unsubscribe(String event) { 176 | messagePackRPC.sendNotification("vim_unsubscribe", event); 177 | } 178 | 179 | public CompletableFuture nameToColor(String name) { 180 | return messagePackRPC.sendRequest(Long.class, "vim_name_to_color", name); 181 | } 182 | 183 | public CompletableFuture call(Class type, String name, Object... args) { 184 | return messagePackRPC.sendRequest(type, name, args); 185 | } 186 | 187 | public void notify(String name, Object... args) { 188 | messagePackRPC.sendNotification(name, args); 189 | } 190 | 191 | // TODO: vim_get_color_map 192 | // TODO: vim_get_api_info 193 | 194 | @Override 195 | public void close() throws IOException { 196 | messagePackRPC.close(); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/NeovimHandler.java: -------------------------------------------------------------------------------- 1 | package com.neovim; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Inherited; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Inherited 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target(ElementType.METHOD) 12 | public @interface NeovimHandler { 13 | String value(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/NeovimModule.java: -------------------------------------------------------------------------------- 1 | package com.neovim; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.core.JsonParseException; 5 | import com.fasterxml.jackson.core.JsonParser; 6 | import com.fasterxml.jackson.core.JsonProcessingException; 7 | import com.fasterxml.jackson.databind.DeserializationContext; 8 | import com.fasterxml.jackson.databind.JsonDeserializer; 9 | import com.fasterxml.jackson.databind.JsonSerializer; 10 | import com.fasterxml.jackson.databind.SerializerProvider; 11 | import com.fasterxml.jackson.databind.module.SimpleModule; 12 | import com.neovim.msgpack.MessagePackRPC; 13 | import org.msgpack.core.MessagePack; 14 | import org.msgpack.jackson.dataformat.MessagePackExtensionType; 15 | import org.msgpack.jackson.dataformat.MessagePackGenerator; 16 | 17 | import java.io.ByteArrayOutputStream; 18 | import java.io.IOException; 19 | import java.util.function.BiFunction; 20 | 21 | import static com.google.common.base.Preconditions.checkNotNull; 22 | 23 | public class NeovimModule extends SimpleModule { 24 | // TODO: Change from hardcoded values to values retrieved from getApiInfo 25 | private static final byte bufferType = 0; 26 | private static final byte windowType = 1; 27 | private static final byte tabPageType = 2; 28 | 29 | private final MessagePackRPC messagePackRPC; 30 | 31 | public NeovimModule(MessagePackRPC messagePackRPC) { 32 | super("Neovim Module"); 33 | this.messagePackRPC = messagePackRPC; 34 | } 35 | 36 | @Override 37 | public void setupModule(SetupContext context) { 38 | addDeserializer(Buffer.class, new IdDeserializer<>(Buffer.class, Buffer::new, bufferType)); 39 | addSerializer(Buffer.class, new JsonSerializer() { 40 | @Override 41 | public Class handledType() { 42 | return Buffer.class; 43 | } 44 | 45 | @Override 46 | public void serialize( 47 | Buffer buffer, 48 | JsonGenerator jsonGenerator, 49 | SerializerProvider serializerProvider) 50 | throws IOException, JsonProcessingException { 51 | MessagePackGenerator generator = (MessagePackGenerator) jsonGenerator; 52 | 53 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 54 | MessagePack.newDefaultPacker(out).packLong(buffer.getId()).close(); 55 | MessagePackExtensionType extensionType = 56 | new MessagePackExtensionType(bufferType, out.toByteArray()); 57 | generator.writeExtensionType(extensionType); 58 | } 59 | }); 60 | 61 | addDeserializer(Window.class, new IdDeserializer<>(Window.class, Window::new, windowType)); 62 | addSerializer(Window.class, new JsonSerializer() { 63 | @Override 64 | public Class handledType() { 65 | return Window.class; 66 | } 67 | 68 | @Override 69 | public void serialize( 70 | Window buffer, 71 | JsonGenerator jsonGenerator, 72 | SerializerProvider serializerProvider) 73 | throws IOException, JsonProcessingException { 74 | MessagePackGenerator generator = (MessagePackGenerator) jsonGenerator; 75 | 76 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 77 | MessagePack.newDefaultPacker(out).packLong(buffer.getId()).close(); 78 | MessagePackExtensionType extensionType = 79 | new MessagePackExtensionType(windowType, out.toByteArray()); 80 | generator.writeExtensionType(extensionType); 81 | } 82 | }); 83 | 84 | addDeserializer( 85 | TabPage.class, new IdDeserializer<>(TabPage.class, TabPage::new, tabPageType)); 86 | addSerializer(TabPage.class, new JsonSerializer() { 87 | @Override 88 | public Class handledType() { 89 | return TabPage.class; 90 | } 91 | 92 | @Override 93 | public void serialize( 94 | TabPage buffer, 95 | JsonGenerator jsonGenerator, 96 | SerializerProvider serializerProvider) 97 | throws IOException, JsonProcessingException { 98 | MessagePackGenerator generator = (MessagePackGenerator) jsonGenerator; 99 | 100 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 101 | MessagePack.newDefaultPacker(out).packLong(buffer.getId()).close(); 102 | MessagePackExtensionType extensionType = 103 | new MessagePackExtensionType(tabPageType, out.toByteArray()); 104 | generator.writeExtensionType(extensionType); 105 | } 106 | }); 107 | // Adding Serializers and Deserializers must happen before this 108 | super.setupModule(context); 109 | } 110 | 111 | private class IdDeserializer extends JsonDeserializer { 112 | private final Class type; 113 | private final BiFunction supplier; 114 | private final int extType; 115 | 116 | public IdDeserializer( 117 | Class type, BiFunction supplier, int extType) { 118 | this.type = checkNotNull(type); 119 | this.supplier = checkNotNull(supplier); 120 | this.extType = extType; 121 | } 122 | 123 | @Override 124 | public Class handledType() { 125 | return type; 126 | } 127 | 128 | @Override 129 | public T deserialize(JsonParser jsonParser, DeserializationContext context) 130 | throws IOException, JsonProcessingException { 131 | MessagePackExtensionType extensionValue = 132 | (MessagePackExtensionType) jsonParser.getEmbeddedObject(); 133 | if (extensionValue.getType() != extType) { 134 | throw new JsonParseException(String.format("extensionType != %d", extType), 135 | jsonParser.getCurrentLocation()); 136 | } 137 | long id = MessagePack.newDefaultUnpacker(extensionValue.getData()) 138 | .unpackValue() 139 | .asIntegerValue() 140 | .asLong(); 141 | return supplier.apply(messagePackRPC, id); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/Position.java: -------------------------------------------------------------------------------- 1 | package com.neovim; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonFormat; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | 7 | @JsonFormat(shape = JsonFormat.Shape.ARRAY) 8 | public class Position { 9 | @JsonProperty(index = 0) public int row; 10 | @JsonProperty(index = 1) public int col; 11 | 12 | @JsonCreator 13 | public Position( 14 | @JsonProperty(value = "row", index = 0) int row, 15 | @JsonProperty(value = "col", index = 1) int col) { 16 | this.row = row; 17 | this.col = col; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return "Position{" + 23 | "row=" + row + 24 | ", col=" + col + 25 | '}'; 26 | } 27 | 28 | @Override 29 | public boolean equals(Object o) { 30 | if (this == o) { 31 | return true; 32 | } 33 | if (o == null || getClass() != o.getClass()) { 34 | return false; 35 | } 36 | 37 | Position position = (Position) o; 38 | 39 | return row == position.row && col == position.col; 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | return 31 * row + col; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/SocketNeovim.java: -------------------------------------------------------------------------------- 1 | package com.neovim; 2 | 3 | import com.google.common.net.HostAndPort; 4 | import com.neovim.msgpack.MessagePackRPC; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | import java.net.Socket; 10 | 11 | public class SocketNeovim implements MessagePackRPC.Connection { 12 | Socket neovim; 13 | InputStream inputStream; 14 | OutputStream outputStream; 15 | 16 | public SocketNeovim(String hostPortString) throws IOException { 17 | this(HostAndPort.fromString(hostPortString)); 18 | } 19 | 20 | public SocketNeovim(HostAndPort hostAndPort) throws IOException { 21 | neovim = new Socket(hostAndPort.getHostText(), hostAndPort.getPort()); 22 | inputStream = neovim.getInputStream(); 23 | outputStream = neovim.getOutputStream(); 24 | } 25 | 26 | @Override 27 | public InputStream getInputStream() { 28 | return inputStream; 29 | } 30 | 31 | @Override 32 | public OutputStream getOutputStream() { 33 | return outputStream; 34 | } 35 | 36 | @Override 37 | public void close() throws IOException { 38 | neovim.close(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/TabPage.java: -------------------------------------------------------------------------------- 1 | package com.neovim; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.google.common.base.Objects; 5 | import com.neovim.msgpack.MessagePackRPC; 6 | 7 | import java.io.IOException; 8 | import java.util.List; 9 | import java.util.concurrent.CompletableFuture; 10 | 11 | import static com.google.common.base.Preconditions.checkNotNull; 12 | 13 | public class TabPage { 14 | private final MessagePackRPC messagePackRPC; 15 | private final long id; 16 | 17 | TabPage(MessagePackRPC messagePackRPC, long id) { 18 | this.messagePackRPC = checkNotNull(messagePackRPC); 19 | this.id = id; 20 | } 21 | 22 | long getId() { 23 | return id; 24 | } 25 | 26 | public CompletableFuture> getWindows() { 27 | return messagePackRPC.sendRequest( 28 | new TypeReference>() {}, "tabpage_get_windows", this); 29 | } 30 | 31 | public CompletableFuture getVar(TypeReference type, String name) { 32 | return messagePackRPC.sendRequest(type, "tabpage_get_var", this, name); 33 | } 34 | 35 | public CompletableFuture setVar(TypeReference type, String name, T value) { 36 | return messagePackRPC.sendRequest(type, "tabpage_set_var", this, name, value); 37 | } 38 | 39 | public CompletableFuture getWindow() { 40 | return messagePackRPC.sendRequest(Window.class, "tabpage_get_window", this); 41 | } 42 | 43 | public CompletableFuture isValid() { 44 | return messagePackRPC.sendRequest(Boolean.class, "tabpage_is_valid", this); 45 | } 46 | 47 | @Override 48 | public boolean equals(Object o) { 49 | if (this == o) return true; 50 | if (o == null || getClass() != o.getClass()) return false; 51 | 52 | TabPage that = (TabPage) o; 53 | 54 | return Objects.equal(this.messagePackRPC, that.messagePackRPC) && 55 | Objects.equal(this.id, that.id); 56 | } 57 | 58 | @Override 59 | public int hashCode() { 60 | return Objects.hashCode(messagePackRPC, id); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/Window.java: -------------------------------------------------------------------------------- 1 | package com.neovim; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.google.common.base.Objects; 5 | import com.neovim.msgpack.MessagePackRPC; 6 | 7 | import java.util.concurrent.CompletableFuture; 8 | 9 | import static com.google.common.base.Preconditions.checkNotNull; 10 | 11 | public class Window { 12 | private final MessagePackRPC messagePackRPC; 13 | private final long id; 14 | 15 | Window(MessagePackRPC messagePackRPC, long id) { 16 | this.messagePackRPC = checkNotNull(messagePackRPC); 17 | this.id = id; 18 | } 19 | 20 | long getId() { 21 | return id; 22 | } 23 | 24 | public CompletableFuture getBuffer() { 25 | return messagePackRPC.sendRequest(Buffer.class, "window_get_buffer", this); 26 | } 27 | 28 | public CompletableFuture getCursorPosition() { 29 | return messagePackRPC.sendRequest(Position.class, "window_get_cursor", this); 30 | } 31 | 32 | public void setCursorPosition(Position pos) { 33 | messagePackRPC.sendNotification("window_set_cursor", this, pos); 34 | } 35 | 36 | public CompletableFuture getHeight() { 37 | return messagePackRPC.sendRequest(Long.class, "window_get_height", this); 38 | } 39 | 40 | public void setHeight(long height) { 41 | messagePackRPC.sendNotification("window_set_height", this, height); 42 | } 43 | 44 | public CompletableFuture getWidth() { 45 | return messagePackRPC.sendRequest(Long.class, "window_get_width", this); 46 | } 47 | 48 | public void setWidth(long width) { 49 | messagePackRPC.sendNotification("window_set_width", this, width); 50 | } 51 | 52 | public CompletableFuture getVar(TypeReference type, String name) { 53 | return messagePackRPC.sendRequest(type, "window_get_var", this, name); 54 | } 55 | 56 | public CompletableFuture setVar(TypeReference type, String name, T value) { 57 | return messagePackRPC.sendRequest(type, "window_set_var", this, name, value); 58 | } 59 | 60 | public CompletableFuture getOption(TypeReference type, String name) { 61 | return messagePackRPC.sendRequest(type, "window_get_option", this, name); 62 | } 63 | 64 | public CompletableFuture setOption(TypeReference type, String name, T value) { 65 | return messagePackRPC.sendRequest(type, "window_set_option", this, name, value); 66 | } 67 | 68 | public CompletableFuture getPosition() { 69 | return messagePackRPC.sendRequest(Position.class, "window_get_position", this); 70 | } 71 | 72 | public CompletableFuture getTabPage() { 73 | return messagePackRPC.sendRequest(TabPage.class, "window_get_tabpage", this); 74 | } 75 | 76 | public CompletableFuture isValid() { 77 | return messagePackRPC.sendRequest(Boolean.class, "window_is_valid", this); 78 | } 79 | 80 | @Override 81 | public boolean equals(Object o) { 82 | if (this == o) return true; 83 | if (o == null || getClass() != o.getClass()) return false; 84 | 85 | Window that = (Window) o; 86 | 87 | return Objects.equal(this.messagePackRPC, that.messagePackRPC) && 88 | Objects.equal(this.id, that.id); 89 | } 90 | 91 | @Override 92 | public int hashCode() { 93 | return Objects.hashCode(messagePackRPC, id); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/msgpack/IOBiFunction.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | import java.io.IOException; 4 | import java.io.UncheckedIOException; 5 | import java.util.function.BiFunction; 6 | 7 | @FunctionalInterface 8 | public interface IOBiFunction extends BiFunction { 9 | default R apply(T t, U u) { 10 | try { 11 | return call(t, u); 12 | } catch (IOException e) { 13 | throw new UncheckedIOException(e); 14 | } 15 | } 16 | 17 | R call(T t, U u) throws IOException; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/msgpack/JsonNodeUtil.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.IOException; 8 | import java.util.List; 9 | 10 | public class JsonNodeUtil { 11 | private static final Logger log = LoggerFactory.getLogger(JsonNodeUtil.class); 12 | private JsonNodeUtil() {} 13 | 14 | public static String getText(JsonNode node) { 15 | if (node.isBinary()) { 16 | try { 17 | return new String(node.binaryValue()); 18 | } catch (IOException e) { 19 | throw new AssertionError(e); 20 | } 21 | } 22 | return node.asText(); 23 | } 24 | 25 | public static StringBuilder formatJsonNode(JsonNode node, StringBuilder builder) { 26 | if (node.isArray()) { 27 | builder.append('['); 28 | if (node.size() > 0) { 29 | formatJsonNode(node.get(0), builder); 30 | for (int i = 1; i < node.size(); i++) { 31 | builder.append(", "); 32 | formatJsonNode(node.get(i), builder); 33 | } 34 | } 35 | builder.append(']'); 36 | } else if (node.isBinary()) { 37 | try { 38 | builder.append(new String(node.binaryValue())); 39 | } catch (IOException e) { 40 | builder.append(node.toString()); 41 | } 42 | } else { 43 | builder.append(node.toString()); 44 | } 45 | return builder; 46 | } 47 | 48 | public static String formatJsonNode(JsonNode node) { 49 | return formatJsonNode(node, new StringBuilder()).toString(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/msgpack/MessagePackRPC.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.core.JsonParser; 5 | import com.fasterxml.jackson.core.type.TypeReference; 6 | import com.fasterxml.jackson.databind.JsonNode; 7 | import com.fasterxml.jackson.databind.Module; 8 | import com.fasterxml.jackson.databind.ObjectMapper; 9 | import org.msgpack.jackson.dataformat.MessagePackFactory; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.io.Closeable; 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.io.OutputStream; 17 | import java.io.UncheckedIOException; 18 | import java.util.Optional; 19 | import java.util.concurrent.CompletableFuture; 20 | import java.util.concurrent.CompletionException; 21 | import java.util.concurrent.ConcurrentHashMap; 22 | import java.util.concurrent.ConcurrentMap; 23 | import java.util.concurrent.ExecutionException; 24 | import java.util.concurrent.ExecutorService; 25 | import java.util.concurrent.Executors; 26 | import java.util.concurrent.Future; 27 | import java.util.function.BiConsumer; 28 | import java.util.function.BiFunction; 29 | 30 | import static com.google.common.base.Preconditions.checkArgument; 31 | import static com.google.common.base.Preconditions.checkNotNull; 32 | import static com.google.common.base.Preconditions.checkState; 33 | import static com.neovim.msgpack.JsonNodeUtil.formatJsonNode; 34 | 35 | public class MessagePackRPC implements AutoCloseable { 36 | 37 | /** 38 | * Connection to an RPC Server. Data sent on the output stream should be interpreted by the 39 | * server and the response should be sent back on the input stream. 40 | */ 41 | public interface Connection extends Closeable { 42 | /** 43 | * Stream from server. 44 | * @return InputStream connected for the output of the server. 45 | */ 46 | InputStream getInputStream(); 47 | 48 | /** 49 | * Stream to server. 50 | * @return OutputStream connected to the input of the server. 51 | */ 52 | OutputStream getOutputStream(); 53 | } 54 | 55 | private static final Logger log = LoggerFactory.getLogger(MessagePackRPC.class); 56 | 57 | private final RequestIdGenerator idGenerator; 58 | private final Connection connection; 59 | private final ExecutorService executorService = Executors.newSingleThreadExecutor(); 60 | private final ObjectMapper objectMapper; 61 | private BiConsumer notificationHandler; 62 | private BiFunction requestHandler; 63 | 64 | private final ConcurrentMap> callbacks = new ConcurrentHashMap<>(); 65 | 66 | private Future receiverFuture = null; 67 | private volatile boolean closed = false; 68 | 69 | public static ObjectMapper defaultObjectMapper() { 70 | MessagePackFactory factory = new MessagePackFactory(); 71 | factory.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE); 72 | factory.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); 73 | return new ObjectMapper(factory); 74 | } 75 | 76 | public MessagePackRPC(Connection connection) { 77 | this(connection, defaultObjectMapper()); 78 | } 79 | 80 | public MessagePackRPC(Connection connection, ObjectMapper objectMapper) { 81 | this(connection, objectMapper, new RequestIdGenerator()); 82 | } 83 | 84 | public MessagePackRPC(Connection connection, ObjectMapper objectMapper, RequestIdGenerator idGenerator) { 85 | this.idGenerator = checkNotNull(idGenerator); 86 | this.objectMapper = checkNotNull(objectMapper); 87 | this.connection = checkNotNull(connection); 88 | notificationHandler = (method, arg) -> log.warn("Received notification {}({})", method, arg); 89 | requestHandler = (method, arg) -> new NeovimException(-1, "Does not support Requests"); 90 | } 91 | 92 | // TODO: Determine if this should be on a separate thread 93 | private synchronized void send(Packet packet) throws IOException { 94 | OutputStream output = connection.getOutputStream(); 95 | objectMapper.writeValue(output, packet); 96 | output.flush(); 97 | } 98 | 99 | private CompletableFuture sendRequest(Request data, RequestCallback callback) { 100 | // Make sure the id is not already in use. (Should never loop) 101 | long id; 102 | do { 103 | id = idGenerator.nextId(); 104 | data.setRequestId(id); 105 | } while(callbacks.putIfAbsent(id, callback) != null); 106 | 107 | try { 108 | send(data); 109 | } catch (IOException e) { 110 | callbacks.remove(id); 111 | callback.getCompletableFuture().completeExceptionally(e); 112 | throw new UncheckedIOException(e); 113 | } 114 | return callback.getCompletableFuture(); 115 | } 116 | 117 | public CompletableFuture sendRequest( 118 | TypeReference typeReference, String functionName, Object... args) { 119 | return sendRequest( 120 | new Request(functionName, args), 121 | new RequestCallback<>(objectMapper.constructType(typeReference.getType()))); 122 | } 123 | 124 | public CompletableFuture sendRequest( 125 | Class resultClass, String functionName, Object... args) { 126 | return sendRequest( 127 | new Request(functionName, args), 128 | new RequestCallback<>(objectMapper.constructType(resultClass))); 129 | } 130 | 131 | /** 132 | * send Message Pack notification rpc 133 | * 134 | * @param functionName The function to be called. 135 | * @param args the arguments to the function. 136 | * 137 | * @throws UncheckedIOException if message fails to be sent. 138 | */ 139 | public void sendNotification(String functionName, Object... args) { 140 | try { 141 | send(new Notification(functionName, args)); 142 | } catch (IOException e) { 143 | throw new UncheckedIOException(e); 144 | } 145 | } 146 | 147 | public void setRequestHandler(BiFunction requestHandler) { 148 | this.requestHandler = checkNotNull(requestHandler); 149 | } 150 | 151 | /** 152 | * Set notification handler. The handler will be passed the function name as a String and the argument as a 153 | * MessagePack Value. It is up to the handler to decode the argument properly. 154 | * @param notificationHandler the notification handler that should be used when a notification is received. 155 | * @throws NullPointerException if notificationHandler is null 156 | */ 157 | public void setNotificationHandler(BiConsumer notificationHandler) { 158 | this.notificationHandler = checkNotNull(notificationHandler); 159 | } 160 | 161 | /** 162 | * Start reader threads. Can only be called once. 163 | * @throws IllegalStateException if called more than once 164 | */ 165 | public void start() { 166 | checkState(receiverFuture == null, "Already Started"); 167 | receiverFuture = executorService.submit(this::readFromInput); 168 | } 169 | 170 | private void readFromInput() { 171 | try { 172 | JsonNode jsonNode; 173 | while ((jsonNode = objectMapper.readTree(connection.getInputStream())) != null) { 174 | log.warn("{}", formatJsonNode(jsonNode)); 175 | if (!jsonNode.isArray()) { 176 | log.error("Received {}, ignoring...", jsonNode); 177 | continue; 178 | } 179 | parsePacket(jsonNode); 180 | } 181 | } catch (IOException e) { 182 | if (!closed) { 183 | log.error("Input Stream error before closed: {}", e.getMessage(), e); 184 | throw new UncheckedIOException("Stream threw exception before closing", e); 185 | } 186 | } 187 | } 188 | 189 | private void parsePacket(JsonNode node) { 190 | checkArgument(node.isArray(), "Node needs to be an array"); 191 | checkArgument(node.size() == 3 || node.size() == 4); 192 | 193 | int type = node.get(0).asInt(-1); 194 | switch (type) { 195 | case Packet.NOTIFICATION_ID: 196 | parseNotification(node); 197 | break; 198 | case Packet.REQUEST_ID: 199 | parseRequest(node); 200 | break; 201 | case Packet.RESPONSE_ID: 202 | parseResponse(node); 203 | break; 204 | default: 205 | throw new IllegalStateException("Not a Notification or Response " + node); 206 | } 207 | } 208 | 209 | private void parseRequest(JsonNode node) { 210 | checkArgument(node.isArray(), "Node needs to be an array"); 211 | checkArgument(node.size() == 4, "Request array should be size 4"); 212 | 213 | long requestId = node.get(1).asLong(); 214 | String method = JsonNodeUtil.getText(node.get(2)); 215 | JsonNode arg = node.get(3); 216 | Object result = requestHandler.apply(method, arg); 217 | try { 218 | send(new Response(requestId, result)); 219 | } catch (IOException e) { 220 | log.error("failed to send response: {}", e.getMessage(), e); 221 | } 222 | } 223 | 224 | private void parseNotification(JsonNode node) { 225 | checkArgument(node.isArray(), "Node needs to be an array"); 226 | checkArgument(node.size() == 3, "Notification array should be size 3"); 227 | 228 | String method = JsonNodeUtil.getText(node.get(1)); 229 | JsonNode arg = node.get(2); 230 | notificationHandler.accept(method, arg); 231 | } 232 | 233 | private void parseResponse(JsonNode node) { 234 | checkArgument(node.isArray(), "Node needs to be an array"); 235 | checkArgument(node.size() == 4, "Response array should be size 4"); 236 | 237 | long requestId = node.get(1).asLong(); 238 | RequestCallback callback = callbacks.get(requestId); 239 | if (callback == null) { 240 | log.warn( 241 | "Response received for {}, However no request was found with that id", 242 | requestId); 243 | return; 244 | } 245 | Optional neovimException = NeovimException.parseError(node.get(2)); 246 | if (neovimException.isPresent()) { 247 | callback.setError(neovimException.get()); 248 | } else { 249 | callback.setResult(objectMapper, node.get(3)); 250 | } 251 | } 252 | 253 | public void registerModule(Module module) { 254 | this.objectMapper.registerModule(module); 255 | } 256 | 257 | @Override 258 | public void close() throws IOException { 259 | closed = true; 260 | connection.close(); 261 | executorService.shutdown(); 262 | if (receiverFuture != null) { 263 | // Check to see if receiver thread had an exception 264 | try { 265 | receiverFuture.get(); 266 | } catch (InterruptedException e) { 267 | executorService.shutdownNow(); 268 | Thread.currentThread().interrupt(); 269 | } catch (ExecutionException e) { 270 | throw new CompletionException(e.getMessage(), e.getCause()); 271 | } 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/msgpack/NeovimException.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import org.msgpack.core.MessagePacker; 5 | 6 | import java.io.IOException; 7 | import java.util.Optional; 8 | 9 | public class NeovimException extends RuntimeException { 10 | private final long errorCode; 11 | 12 | public NeovimException(long errorCode, String errorMessage) { 13 | super(errorMessage); 14 | this.errorCode = errorCode; 15 | } 16 | 17 | public long getErrorCode() { 18 | return errorCode; 19 | } 20 | 21 | public static Optional parseError(JsonNode node) { 22 | if (node.isNull()) { 23 | return Optional.empty(); 24 | } 25 | 26 | if (node.isArray() && node.size() == 2) { 27 | if (node.get(0).isIntegralNumber()) { 28 | long errorCode = node.get(0).asLong(); 29 | String errorMessage = JsonNodeUtil.getText(node.get(1)); 30 | return Optional.of(new NeovimException(errorCode, errorMessage)); 31 | } 32 | } 33 | return Optional.of(new NeovimException(-1, "Unknown Error: " + node)); 34 | } 35 | 36 | public void serialize(MessagePacker packer) throws IOException { 37 | packer.packArrayHeader(2); 38 | packer.packLong(errorCode); 39 | packer.packString(getMessage()); 40 | } 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/msgpack/Notification.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | 8 | @JsonFormat(shape = JsonFormat.Shape.ARRAY) 9 | public class Notification implements Packet { 10 | private int type = NOTIFICATION_ID; 11 | private String method; 12 | private ArrayList args = new ArrayList<>(); 13 | 14 | public Notification(String method, Object... args) { 15 | this.method = method; 16 | this.args = new ArrayList<>(Arrays.asList(args)); 17 | } 18 | 19 | public int getType() { 20 | return type; 21 | } 22 | 23 | public String getMethod() { 24 | return method; 25 | } 26 | 27 | public ArrayList getArgs() { 28 | return args; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "Notification{" + 34 | "type=" + type + 35 | ", method='" + method + '\'' + 36 | ", args=" + args + 37 | '}'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/msgpack/Packet.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | public interface Packet { 4 | int REQUEST_ID = 0; 5 | int RESPONSE_ID = 1; 6 | int NOTIFICATION_ID = 2; 7 | 8 | int getType(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/msgpack/Request.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | 8 | @JsonFormat(shape = JsonFormat.Shape.ARRAY) 9 | public class Request implements Packet { 10 | 11 | private int type = REQUEST_ID; 12 | private long requestId; 13 | private String method; 14 | private ArrayList args = new ArrayList<>(); 15 | 16 | public Request(String method, Object... parameters) { 17 | this.method = method; 18 | this.args.addAll(Arrays.asList(parameters)); 19 | } 20 | 21 | @Override 22 | public int getType() { 23 | return type; 24 | } 25 | 26 | public String getMethod() { 27 | return method; 28 | } 29 | 30 | public long getRequestId() { 31 | return requestId; 32 | } 33 | 34 | public ArrayList getArgs() { 35 | return args; 36 | } 37 | 38 | public void setRequestId(long requestId) { 39 | this.requestId = requestId; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "Request{" + 45 | "type=" + type + 46 | ", requestId=" + requestId + 47 | ", method='" + method + '\'' + 48 | ", args=" + args + 49 | '}'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/msgpack/RequestCallback.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | import com.fasterxml.jackson.databind.JavaType; 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.msgpack.core.MessageFormatException; 7 | 8 | import java.io.IOException; 9 | import java.util.concurrent.CompletableFuture; 10 | 11 | public class RequestCallback { 12 | 13 | private final IOBiFunction deserializer; 14 | private final CompletableFuture completableFuture = new CompletableFuture(); 15 | 16 | public RequestCallback(JavaType type) { 17 | this.deserializer = (objectMapper, node) -> objectMapper.readValue(node.traverse(), type); 18 | } 19 | 20 | public void setResult(ObjectMapper objectMapper, JsonNode result) { 21 | try { 22 | completableFuture.complete(deserializer.call(objectMapper, result)); 23 | } catch (IOException | MessageFormatException e) { 24 | completableFuture.completeExceptionally(e); 25 | } 26 | } 27 | 28 | public void setError(NeovimException error) { 29 | completableFuture.completeExceptionally(error); 30 | } 31 | 32 | public CompletableFuture getCompletableFuture() { 33 | return completableFuture; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/msgpack/RequestIdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | 5 | /** 6 | * Generator for MessagePack request ids. Requests ids must must be 32 bit unsigned integers. 7 | */ 8 | public class RequestIdGenerator { 9 | private final AtomicInteger id; 10 | 11 | /** 12 | * Generator that starts where the first Id is 0. 13 | */ 14 | public RequestIdGenerator() { 15 | this(0); 16 | } 17 | 18 | /** 19 | * Initialize the generator to the initialValue treated as an unsigned integer 20 | * 21 | * @param initialValue unsigned integer value used for initial id. 22 | */ 23 | public RequestIdGenerator(int initialValue) { 24 | id = new AtomicInteger(initialValue); 25 | } 26 | 27 | /** 28 | * Get the next id. 29 | * 30 | * @return next id where id is between 0 and 2^32 - 1 (32 bit unsigned integer range) 31 | */ 32 | public long nextId() { 33 | // Make sure Id is in the range of 0 to 2^32 - 1 34 | return Integer.toUnsignedLong(id.getAndIncrement()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/neovim/msgpack/Response.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | 5 | import java.util.Arrays; 6 | 7 | @JsonFormat(shape = JsonFormat.Shape.ARRAY) 8 | public class Response implements Packet { 9 | private int type = RESPONSE_ID; 10 | private long requestId; 11 | private NeovimException exception; 12 | private Object result; 13 | 14 | public Response(long requestId, Object result) { 15 | this.requestId = requestId; 16 | if (result instanceof NeovimException) { 17 | this.exception = (NeovimException) result; 18 | } else { 19 | this.result = result; 20 | } 21 | } 22 | 23 | @Override 24 | public int getType() { 25 | return type; 26 | } 27 | 28 | public long getRequestId() { 29 | return requestId; 30 | } 31 | 32 | public Object getException() { 33 | if (exception == null) { 34 | return null; 35 | } 36 | return Arrays.asList(exception.getErrorCode(), exception.getMessage()); 37 | } 38 | 39 | public Object getResult() { 40 | return result; 41 | } 42 | 43 | public void setError(NeovimException exception) { 44 | this.exception = exception; 45 | this.result = null; 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return "Response{" + 51 | "requestId=" + requestId + 52 | ", exception=" + exception + 53 | ", result=" + result + 54 | '}'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/com/neovim/BufferTest.java: -------------------------------------------------------------------------------- 1 | package com.neovim; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.neovim.msgpack.MessagePackRPC; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.Mock; 9 | import org.mockito.runners.MockitoJUnitRunner; 10 | import org.msgpack.core.ExtensionTypeHeader; 11 | import org.msgpack.core.MessagePack; 12 | import org.msgpack.core.MessageUnpacker; 13 | import org.msgpack.jackson.dataformat.MessagePackFactory; 14 | 15 | import java.io.ByteArrayOutputStream; 16 | 17 | import static org.hamcrest.core.Is.is; 18 | import static org.junit.Assert.assertThat; 19 | 20 | @RunWith(MockitoJUnitRunner.class) 21 | public class BufferTest { 22 | private static final byte EXT_TYPE = 0; 23 | private static final long BUFFER_ID = 42L; 24 | private ObjectMapper objectMapper; 25 | private Buffer buffer; 26 | 27 | @Mock MessagePackRPC messagePackRPC; 28 | 29 | @Before 30 | public void setUp() { 31 | objectMapper = new ObjectMapper(new MessagePackFactory()); 32 | NeovimModule module = new NeovimModule(messagePackRPC); 33 | objectMapper.registerModule(module); 34 | buffer = new Buffer(messagePackRPC, BUFFER_ID); 35 | } 36 | 37 | @Test 38 | public void testSerialize() throws Exception { 39 | ByteArrayOutputStream payload = new ByteArrayOutputStream(); 40 | MessagePack.newDefaultPacker(payload) 41 | .packLong(buffer.getId()) 42 | .close(); 43 | 44 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 45 | byte[] payloadContents = payload.toByteArray(); 46 | MessagePack.newDefaultPacker(out) 47 | .packExtensionTypeHeader(EXT_TYPE, payloadContents.length) 48 | .writePayload(payloadContents) 49 | .close(); 50 | 51 | byte[] objectMapperOut = objectMapper.writeValueAsBytes(buffer); 52 | 53 | MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(objectMapperOut); 54 | ExtensionTypeHeader extensionTypeHeader = unpacker.unpackExtensionTypeHeader(); 55 | assertThat(extensionTypeHeader.getLength(), is(1)); 56 | assertThat(extensionTypeHeader.getType(), is(EXT_TYPE)); 57 | byte[] buf = new byte[extensionTypeHeader.getLength()]; 58 | unpacker.readPayload(buf); 59 | assertThat(buf, is(payload.toByteArray())); 60 | 61 | assertThat(objectMapperOut, is(out.toByteArray())); 62 | } 63 | 64 | @Test 65 | public void testDeserialize() throws Exception { 66 | ByteArrayOutputStream payload = new ByteArrayOutputStream(); 67 | MessagePack.newDefaultPacker(payload) 68 | .packLong(buffer.getId()) 69 | .close(); 70 | 71 | byte[] payloadContents = payload.toByteArray(); 72 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 73 | MessagePack.newDefaultPacker(out) 74 | .packExtensionTypeHeader(EXT_TYPE, payloadContents.length) 75 | .writePayload(payloadContents) 76 | .close(); 77 | 78 | Buffer b = objectMapper.readValue(out.toByteArray(), Buffer.class); 79 | assertThat(b, is(buffer)); 80 | } 81 | 82 | @Test 83 | public void serializeDeserialize_sameObject() throws Exception { 84 | byte[] serializedValue = objectMapper.writeValueAsBytes(buffer); 85 | Buffer deserializedValue = objectMapper.readValue(serializedValue, Buffer.class); 86 | 87 | assertThat(deserializedValue, is(buffer)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/com/neovim/DispatcherTest.java: -------------------------------------------------------------------------------- 1 | package com.neovim; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.neovim.msgpack.NeovimException; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.mockito.runners.MockitoJUnitRunner; 11 | import org.msgpack.jackson.dataformat.MessagePackFactory; 12 | 13 | import java.io.IOException; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | import java.util.stream.Collectors; 17 | 18 | import static org.hamcrest.Matchers.is; 19 | import static org.junit.Assert.assertThat; 20 | import static org.junit.Assert.fail; 21 | 22 | @RunWith(MockitoJUnitRunner.class) 23 | public class DispatcherTest { 24 | private static final String NAME = "echo"; 25 | private static final String HELLO = "Hello"; 26 | private static final int ONE = 1; 27 | private static final String HELLO_1 = "Hello 1"; 28 | private static final String ERROR_MESSAGE = "Error Message"; 29 | private static final ObjectMapper MAPPER = new ObjectMapper(new MessagePackFactory()); 30 | 31 | Dispatcher dispatcher; 32 | 33 | @Before 34 | public void setUp() { 35 | dispatcher = new Dispatcher(MAPPER); 36 | } 37 | 38 | @Test 39 | public void request_methodArgList_conjoinsArgumentsAsList() throws JsonProcessingException { 40 | dispatcher.register( 41 | new Object() { 42 | @NeovimHandler(NAME) 43 | public String concat(List list) { 44 | return list.stream().collect(Collectors.joining(" ")); 45 | } 46 | } 47 | ); 48 | 49 | JsonNode value = pack(HELLO, ONE); 50 | Object result = dispatcher.dispatchMethod(NAME, value); 51 | System.out.println(result); 52 | 53 | assertThat(result, is(HELLO_1)); 54 | } 55 | 56 | @Test 57 | public void request_methodThrewRuntimeException_MessageInNeovimException() throws 58 | JsonProcessingException { 59 | dispatcher.register( 60 | new Object() { 61 | @NeovimHandler(NAME) 62 | public String request(String string, Integer integer) { 63 | throw new RuntimeException(ERROR_MESSAGE); 64 | } 65 | }); 66 | 67 | JsonNode value = pack(HELLO, ONE); 68 | NeovimException exception = (NeovimException) dispatcher.dispatchMethod(NAME, value); 69 | assertThat(exception.getMessage(), is(ERROR_MESSAGE)); 70 | } 71 | 72 | @Test 73 | public void registerMethod_callsMethod() throws IOException { 74 | dispatcher.register(new Object() { 75 | @NeovimHandler(NAME) 76 | public String concat(String string, Integer integer) { 77 | return string + " " + integer; 78 | } 79 | }); 80 | 81 | JsonNode value = pack(HELLO, ONE); 82 | String result = (String) dispatcher.dispatchMethod(NAME, value); 83 | assertThat(result, is(HELLO_1)); 84 | } 85 | 86 | @Test 87 | public void register_registerTwoNotificationsWithSameName_throwsIllegalStateException() { 88 | try { 89 | dispatcher.register( 90 | new Object() { 91 | @NeovimHandler(NAME) 92 | public void name() {} 93 | 94 | @NeovimHandler(NAME) 95 | public void name2() {} 96 | }); 97 | fail(); 98 | } catch (IllegalStateException expected) {} 99 | } 100 | 101 | @Test 102 | public void oneArg_Integer() throws JsonProcessingException { 103 | dispatcher.register( 104 | new Object() { 105 | @NeovimHandler(NAME) 106 | public Integer identity(Integer i) { 107 | return i; 108 | } 109 | }); 110 | 111 | final Integer i = 1; 112 | Object result = dispatcher.dispatchMethod(NAME, pack(i)); 113 | assertThat(result, is(i)); 114 | } 115 | 116 | @Test 117 | public void varargs_oneArgument() throws JsonProcessingException { 118 | dispatcher.register( 119 | new Object() { 120 | @NeovimHandler(NAME) 121 | public int count(int... i) { 122 | return i.length; 123 | } 124 | }); 125 | 126 | final Integer i = 1; 127 | Object result = dispatcher.dispatchMethod(NAME, pack(i)); 128 | assertThat(result, is(i)); 129 | } 130 | 131 | @Test 132 | public void varargs_twoArgument() throws JsonProcessingException { 133 | dispatcher.register( 134 | new Object() { 135 | @NeovimHandler(NAME) 136 | public int count(int... i) { 137 | return i.length; 138 | } 139 | }); 140 | 141 | final Integer i = 2; 142 | Object result = dispatcher.dispatchMethod(NAME, pack(i, i)); 143 | assertThat(result, is(i)); 144 | } 145 | 146 | @Test 147 | public void varargs_staticArgumentPlusTwoArguments() throws JsonProcessingException { 148 | dispatcher.register( 149 | new Object() { 150 | @NeovimHandler(NAME) 151 | public int count(int x, int... i) { 152 | return Arrays.stream(i).reduce(x, Integer::sum); 153 | } 154 | }); 155 | 156 | JsonNode args = pack(3, 3, 3); 157 | Object result = dispatcher.dispatchMethod(NAME, args); 158 | assertThat(result, is(9)); 159 | } 160 | 161 | @Test 162 | public void register_registerTwoMethodsWithSameName_throwsIllegalStateException() { 163 | try { 164 | dispatcher.register( 165 | new Object() { 166 | @NeovimHandler(NAME) 167 | public Object name() { 168 | return null; 169 | } 170 | 171 | @NeovimHandler(NAME) 172 | public Object name2() { 173 | return null; 174 | } 175 | }); 176 | fail(); 177 | } catch (IllegalStateException expected) {} 178 | } 179 | 180 | @Test 181 | public void register_listAsOnlyArgument_calledWithMultipleElementsInArray() 182 | throws JsonProcessingException { 183 | dispatcher.register( 184 | new Object() { 185 | @NeovimHandler(NAME) 186 | public int func(List args) { 187 | return args.size(); 188 | } 189 | }); 190 | 191 | JsonNode args = pack(3, "Hello World", 3); 192 | Object result = dispatcher.dispatchMethod(NAME, args); 193 | assertThat(result, is(3)); 194 | } 195 | 196 | @Test 197 | public void listOfJsonNodes() throws JsonProcessingException { 198 | dispatcher.register( 199 | new Object() { 200 | @NeovimHandler(NAME) 201 | public int func(JsonNode args) { 202 | System.out.println(args); 203 | return args.size(); 204 | } 205 | } 206 | ); 207 | 208 | JsonNode args = pack((Object) new Object[] { "put", new Object[] { new byte[] { 'a' }}}); 209 | Object result = dispatcher.dispatchMethod(NAME, args); 210 | assertThat(result, is(1)); 211 | } 212 | 213 | private JsonNode pack(Object... objects) throws JsonProcessingException { 214 | return MAPPER.convertValue(objects, JsonNode.class); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/test/java/com/neovim/TabPageTest.java: -------------------------------------------------------------------------------- 1 | package com.neovim; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.neovim.msgpack.MessagePackRPC; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.Mock; 9 | import org.mockito.runners.MockitoJUnitRunner; 10 | import org.msgpack.core.ExtensionTypeHeader; 11 | import org.msgpack.core.MessagePack; 12 | import org.msgpack.core.MessageUnpacker; 13 | import org.msgpack.jackson.dataformat.MessagePackFactory; 14 | 15 | import java.io.ByteArrayOutputStream; 16 | 17 | import static org.hamcrest.core.Is.is; 18 | import static org.junit.Assert.assertThat; 19 | 20 | @RunWith(MockitoJUnitRunner.class) 21 | public class TabPageTest { 22 | private static final byte EXT_TYPE = 2; 23 | private static final long BUFFER_ID = 42L; 24 | private ObjectMapper objectMapper; 25 | private TabPage tabPage; 26 | 27 | @Mock MessagePackRPC messagePackRPC; 28 | 29 | @Before 30 | public void setUp() { 31 | objectMapper = new ObjectMapper(new MessagePackFactory()); 32 | NeovimModule module = new NeovimModule(messagePackRPC); 33 | objectMapper.registerModule(module); 34 | tabPage = new TabPage(messagePackRPC, BUFFER_ID); 35 | } 36 | 37 | @Test 38 | public void testSerialize() throws Exception { 39 | ByteArrayOutputStream payload = new ByteArrayOutputStream(); 40 | MessagePack.newDefaultPacker(payload) 41 | .packLong(tabPage.getId()) 42 | .close(); 43 | 44 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 45 | byte[] payloadContents = payload.toByteArray(); 46 | MessagePack.newDefaultPacker(out) 47 | .packExtensionTypeHeader(EXT_TYPE, payloadContents.length) 48 | .writePayload(payloadContents) 49 | .close(); 50 | 51 | byte[] objectMapperOut = objectMapper.writeValueAsBytes(tabPage); 52 | 53 | MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(objectMapperOut); 54 | ExtensionTypeHeader extensionTypeHeader = unpacker.unpackExtensionTypeHeader(); 55 | assertThat(extensionTypeHeader.getLength(), is(1)); 56 | assertThat(extensionTypeHeader.getType(), is(EXT_TYPE)); 57 | byte[] buf = new byte[extensionTypeHeader.getLength()]; 58 | unpacker.readPayload(buf); 59 | assertThat(buf, is(payload.toByteArray())); 60 | 61 | assertThat(objectMapperOut, is(out.toByteArray())); 62 | } 63 | 64 | @Test 65 | public void testDeserialize() throws Exception { 66 | ByteArrayOutputStream payload = new ByteArrayOutputStream(); 67 | MessagePack.newDefaultPacker(payload) 68 | .packLong(tabPage.getId()) 69 | .close(); 70 | 71 | byte[] payloadContents = payload.toByteArray(); 72 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 73 | MessagePack.newDefaultPacker(out) 74 | .packExtensionTypeHeader(EXT_TYPE, payloadContents.length) 75 | .writePayload(payloadContents) 76 | .close(); 77 | 78 | TabPage t = objectMapper.readValue(out.toByteArray(), TabPage.class); 79 | assertThat(t, is(tabPage)); 80 | } 81 | 82 | @Test 83 | public void serializeDeserialize_sameObject() throws Exception { 84 | byte[] serializedValue = objectMapper.writeValueAsBytes(tabPage); 85 | TabPage deserializedValue = objectMapper.readValue(serializedValue, TabPage.class); 86 | 87 | assertThat(deserializedValue, is(tabPage)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/com/neovim/WindowTest.java: -------------------------------------------------------------------------------- 1 | package com.neovim; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.neovim.msgpack.MessagePackRPC; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.Mock; 9 | import org.mockito.runners.MockitoJUnitRunner; 10 | import org.msgpack.core.ExtensionTypeHeader; 11 | import org.msgpack.core.MessagePack; 12 | import org.msgpack.core.MessageUnpacker; 13 | import org.msgpack.jackson.dataformat.MessagePackFactory; 14 | 15 | import java.io.ByteArrayOutputStream; 16 | 17 | import static org.hamcrest.core.Is.is; 18 | import static org.junit.Assert.assertThat; 19 | 20 | @RunWith(MockitoJUnitRunner.class) 21 | public class WindowTest { 22 | private static final byte EXT_TYPE = 1; 23 | private static final long BUFFER_ID = 42L; 24 | private ObjectMapper objectMapper; 25 | private Window window; 26 | 27 | @Mock MessagePackRPC messagePackRPC; 28 | 29 | @Before 30 | public void setUp() { 31 | objectMapper = new ObjectMapper(new MessagePackFactory()); 32 | NeovimModule module = new NeovimModule(messagePackRPC); 33 | objectMapper.registerModule(module); 34 | window = new Window(messagePackRPC, BUFFER_ID); 35 | } 36 | 37 | @Test 38 | public void testSerialize() throws Exception { 39 | ByteArrayOutputStream payload = new ByteArrayOutputStream(); 40 | MessagePack.newDefaultPacker(payload) 41 | .packLong(window.getId()) 42 | .close(); 43 | 44 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 45 | byte[] payloadContents = payload.toByteArray(); 46 | MessagePack.newDefaultPacker(out) 47 | .packExtensionTypeHeader(EXT_TYPE, payloadContents.length) 48 | .writePayload(payloadContents) 49 | .close(); 50 | 51 | byte[] objectMapperOut = objectMapper.writeValueAsBytes(window); 52 | 53 | MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(objectMapperOut); 54 | ExtensionTypeHeader extensionTypeHeader = unpacker.unpackExtensionTypeHeader(); 55 | assertThat(extensionTypeHeader.getLength(), is(1)); 56 | assertThat(extensionTypeHeader.getType(), is(EXT_TYPE)); 57 | byte[] buf = new byte[extensionTypeHeader.getLength()]; 58 | unpacker.readPayload(buf); 59 | assertThat(buf, is(payload.toByteArray())); 60 | 61 | assertThat(objectMapperOut, is(out.toByteArray())); 62 | } 63 | 64 | @Test 65 | public void testDeserialize() throws Exception { 66 | ByteArrayOutputStream payload = new ByteArrayOutputStream(); 67 | MessagePack.newDefaultPacker(payload) 68 | .packLong(window.getId()) 69 | .close(); 70 | 71 | byte[] payloadContents = payload.toByteArray(); 72 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 73 | MessagePack.newDefaultPacker(out) 74 | .packExtensionTypeHeader(EXT_TYPE, payloadContents.length) 75 | .writePayload(payloadContents) 76 | .close(); 77 | 78 | Window w = objectMapper.readValue(out.toByteArray(), Window.class); 79 | assertThat(w, is(window)); 80 | } 81 | 82 | @Test 83 | public void serializeDeserialize_sameObject() throws Exception { 84 | byte[] serializedValue = objectMapper.writeValueAsBytes(window); 85 | Window deserializedValue = objectMapper.readValue(serializedValue, Window.class); 86 | 87 | assertThat(deserializedValue, is(window)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/com/neovim/msgpack/MessagePackRPCTest.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.ArgumentCaptor; 9 | import org.mockito.Captor; 10 | import org.mockito.Mock; 11 | import org.mockito.runners.MockitoJUnitRunner; 12 | import org.msgpack.jackson.dataformat.MessagePackFactory; 13 | 14 | import java.io.ByteArrayInputStream; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.io.OutputStream; 18 | import java.util.Collections; 19 | import java.util.List; 20 | import java.util.concurrent.CompletionException; 21 | import java.util.function.BiConsumer; 22 | import java.util.function.BiFunction; 23 | 24 | import static com.google.common.primitives.Bytes.concat; 25 | import static org.hamcrest.core.Is.is; 26 | import static org.junit.Assert.assertThat; 27 | import static org.junit.Assert.fail; 28 | import static org.mockito.Matchers.eq; 29 | import static org.mockito.Mockito.mock; 30 | import static org.mockito.Mockito.times; 31 | import static org.mockito.Mockito.verify; 32 | import static org.mockito.Mockito.when; 33 | import static org.msgpack.core.Preconditions.checkNotNull; 34 | 35 | @RunWith(MockitoJUnitRunner.class) 36 | public class MessagePackRPCTest { 37 | private static final String METHOD = "method"; 38 | private static final Integer ARG = 1; 39 | private static final List ARGS = Collections.singletonList(ARG); 40 | private static final long REQUEST_ID = 1234L; 41 | private static final ObjectMapper MAPPER = new ObjectMapper(new MessagePackFactory()); 42 | 43 | private MessagePackRPC messagePackRPC; 44 | 45 | @Mock ObjectMapper objectMapper; 46 | @Mock InputStream inputStream; 47 | @Mock OutputStream outputStream; 48 | @Mock RequestIdGenerator idGenerator; 49 | @Mock BiConsumer notificationHandler; 50 | @Mock BiFunction requestHandler; 51 | 52 | @Captor ArgumentCaptor stringCaptor; 53 | @Captor ArgumentCaptor valueCaptor; 54 | @Captor ArgumentCaptor byteArrayCaptor; 55 | @Captor ArgumentCaptor packetCaptor; 56 | @Captor ArgumentCaptor offsetCaptor; 57 | @Captor ArgumentCaptor lengthCaptor; 58 | 59 | @Before 60 | public void setUp() throws Exception { 61 | TestConnection testConnection = new TestConnection(inputStream, outputStream); 62 | 63 | messagePackRPC = new MessagePackRPC(testConnection, objectMapper, idGenerator); 64 | } 65 | 66 | @Test 67 | public void receiverThread_binaryMethodName_callsNotificationHandlerOnNotification() 68 | throws Exception { 69 | MessagePackRPC messagePackRPC 70 | = withInput(pack(Packet.NOTIFICATION_ID, METHOD.getBytes(), ARGS)); 71 | messagePackRPC.setNotificationHandler(notificationHandler); 72 | // Start Receiver Thread 73 | messagePackRPC.start(); 74 | 75 | // Join with receiver thread 76 | messagePackRPC.close(); 77 | 78 | verify(notificationHandler).accept(stringCaptor.capture(), valueCaptor.capture()); 79 | assertThat(stringCaptor.getValue(), is(METHOD)); 80 | JsonNode value = valueCaptor.getValue(); 81 | assertThat(value.isArray(), is(true)); 82 | assertThat(value.size(), is(1)); 83 | assertThat(value.get(0).asInt(), is(ARG)); 84 | } 85 | 86 | @Test 87 | public void receiverThread_callsNotificationHandlerOnNotification_twice() throws Exception { 88 | byte[] input = pack(Packet.NOTIFICATION_ID, METHOD, ARGS); 89 | MessagePackRPC messagePackRPC = withInput(concat(input, input)); 90 | messagePackRPC.setNotificationHandler(notificationHandler); 91 | // Start Receiver Thread 92 | messagePackRPC.start(); 93 | 94 | // Join with receiver thread 95 | messagePackRPC.close(); 96 | 97 | verify(notificationHandler, times(2)).accept(stringCaptor.capture(), valueCaptor.capture()); 98 | assertThat(stringCaptor.getValue(), is(METHOD)); 99 | JsonNode value = valueCaptor.getValue(); 100 | assertThat(value.isArray(), is(true)); 101 | assertThat(value.size(), is(1)); 102 | assertThat(value.get(0).asInt(), is(ARG)); 103 | } 104 | 105 | @Test 106 | public void receiverThread_stringMethodName_callsNotificationHandlerOnNotification() 107 | throws Exception { 108 | MessagePackRPC messagePackRPC = withInput(pack(Packet.NOTIFICATION_ID, METHOD, ARGS)); 109 | messagePackRPC.setNotificationHandler(notificationHandler); 110 | // Start Receiver Thread 111 | messagePackRPC.start(); 112 | 113 | // Join with receiver thread 114 | messagePackRPC.close(); 115 | 116 | verify(notificationHandler).accept(stringCaptor.capture(), valueCaptor.capture()); 117 | assertThat(stringCaptor.getValue(), is(METHOD)); 118 | JsonNode value = valueCaptor.getValue(); 119 | assertThat(value.isArray(), is(true)); 120 | assertThat(value.size(), is(1)); 121 | assertThat(value.get(0).asInt(), is(ARG)); 122 | } 123 | 124 | @Test 125 | public void receiverThread_binaryMethodName_callsRequestHandlerOnRequest() throws Exception { 126 | MessagePackRPC messagePackRPC 127 | = withInput(pack(Packet.REQUEST_ID, REQUEST_ID, METHOD.getBytes(), ARGS)); 128 | messagePackRPC.setRequestHandler(requestHandler); 129 | // Start Receiver Thread 130 | messagePackRPC.start(); 131 | 132 | // Join with receiver thread 133 | messagePackRPC.close(); 134 | 135 | verify(requestHandler).apply(stringCaptor.capture(), valueCaptor.capture()); 136 | assertThat(stringCaptor.getValue(), is(METHOD)); 137 | JsonNode value = valueCaptor.getValue(); 138 | assertThat(value.isArray(), is(true)); 139 | assertThat(value.size(), is(1)); 140 | assertThat(value.get(0).asInt(), is(ARG)); 141 | } 142 | 143 | @Test 144 | public void receiverThread_stringMethodName_callsRequestHandlerOnRequest() throws Exception { 145 | MessagePackRPC messagePackRPC 146 | = withInput(pack(Packet.REQUEST_ID, REQUEST_ID, METHOD, ARGS)); 147 | messagePackRPC.setRequestHandler(requestHandler); 148 | // Start Receiver Thread 149 | messagePackRPC.start(); 150 | 151 | // Join with receiver thread 152 | messagePackRPC.close(); 153 | 154 | verify(requestHandler).apply(stringCaptor.capture(), valueCaptor.capture()); 155 | assertThat(stringCaptor.getValue(), is(METHOD)); 156 | JsonNode value = valueCaptor.getValue(); 157 | assertThat(value.isArray(), is(true)); 158 | assertThat(value.size(), is(1)); 159 | assertThat(value.get(0).asInt(), is(ARG)); 160 | } 161 | 162 | @Test 163 | public void sendRequest_callsObjectMapperWithRequest() throws Exception { 164 | when(idGenerator.nextId()).thenReturn(REQUEST_ID); 165 | messagePackRPC.sendRequest(Object.class, METHOD, ARG); 166 | 167 | verify(objectMapper).writeValue(eq(outputStream), packetCaptor.capture()); 168 | verify(outputStream).flush(); 169 | 170 | Request request = (Request) packetCaptor.getValue(); 171 | assertThat(request.getRequestId(), is(REQUEST_ID)); 172 | assertThat(request.getMethod(), is(METHOD)); 173 | assertThat(request.getArgs(), is(ARGS)); 174 | } 175 | 176 | @Test 177 | public void sendNotification_callsObjectMapperWithNotification() throws Exception { 178 | messagePackRPC.sendNotification(METHOD, ARG); 179 | 180 | verify(objectMapper).writeValue(eq(outputStream), packetCaptor.capture()); 181 | verify(outputStream).flush(); 182 | 183 | Notification request = (Notification) packetCaptor.getValue(); 184 | assertThat(request.getMethod(), is(METHOD)); 185 | assertThat(request.getArgs(), is(ARGS)); 186 | } 187 | 188 | @Test 189 | public void close_receiverThreadException_wrappedInCompletionException() 190 | throws IOException, InterruptedException { 191 | RuntimeException exception = new RuntimeException(); 192 | when(objectMapper.readTree(inputStream)).thenThrow(exception); 193 | messagePackRPC.start(); 194 | 195 | try { 196 | messagePackRPC.close(); 197 | fail(); 198 | } catch (CompletionException e) { 199 | assertThat(e.getCause(), is(exception)); 200 | } 201 | } 202 | 203 | @Test 204 | public void close_callsConnectionClose() throws Exception { 205 | MessagePackRPC.Connection connection = mock(MessagePackRPC.Connection.class); 206 | MessagePackRPC messagePackRPC = new MessagePackRPC(connection); 207 | messagePackRPC.close(); 208 | 209 | verify(connection).close(); 210 | } 211 | 212 | private class TestConnection implements MessagePackRPC.Connection { 213 | 214 | private final InputStream inputStream; 215 | private final OutputStream outputStream; 216 | 217 | public TestConnection(InputStream inputStream, OutputStream outputStream) { 218 | this.inputStream = checkNotNull(inputStream); 219 | this.outputStream = checkNotNull(outputStream); 220 | } 221 | 222 | @Override 223 | public InputStream getInputStream() { 224 | return inputStream; 225 | } 226 | 227 | @Override 228 | public OutputStream getOutputStream() { 229 | return outputStream; 230 | } 231 | 232 | @Override 233 | public void close() throws IOException {} 234 | } 235 | 236 | public static byte[] pack(Object... args) throws IOException { 237 | return MAPPER.writeValueAsBytes(args); 238 | } 239 | 240 | public MessagePackRPC withInput(byte[] buffer) { 241 | ByteArrayInputStream input = new ByteArrayInputStream(buffer); 242 | return new MessagePackRPC(new TestConnection(input, outputStream)); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/test/java/com/neovim/msgpack/NeovimExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.junit.Test; 7 | import org.msgpack.jackson.dataformat.MessagePackFactory; 8 | 9 | import java.io.IOException; 10 | import java.nio.charset.StandardCharsets; 11 | import java.util.Optional; 12 | 13 | import static org.hamcrest.Matchers.startsWith; 14 | import static org.hamcrest.core.Is.is; 15 | import static org.junit.Assert.assertThat; 16 | 17 | public class NeovimExceptionTest { 18 | private static final ObjectMapper MAPPER = new ObjectMapper(new MessagePackFactory()); 19 | 20 | private static final long ERROR_CODE = 42L; 21 | private static final String ERROR_MESSAGE = "ERROR_MESSAGE"; 22 | 23 | private static final long FAILED_ERROR_CODE = -1L; 24 | private static final String FAILED_ERROR_MESSAGE_START = "Unknown Error: "; 25 | 26 | @Test 27 | public void parseError_arrayOfLongAndString_successful() throws Exception { 28 | JsonNode value = pack(ERROR_CODE, ERROR_MESSAGE.getBytes(StandardCharsets.UTF_8)); 29 | 30 | Optional neovimException = NeovimException.parseError(value); 31 | assertThat(neovimException.isPresent(), is(true)); 32 | assertThat(neovimException.get().getErrorCode(), is(ERROR_CODE)); 33 | assertThat(neovimException.get().getMessage(), is(ERROR_MESSAGE)); 34 | } 35 | 36 | @Test 37 | public void parseError_nilReference_emptyOptional() throws JsonProcessingException { 38 | JsonNode value = MAPPER.getNodeFactory().nullNode(); 39 | Optional neovimException = NeovimException.parseError(value); 40 | 41 | assertThat(neovimException.isPresent(), is(false)); 42 | } 43 | 44 | @Test 45 | public void parseError_unknownValueRef_returnsException() throws IOException { 46 | JsonNode value = pack(ERROR_CODE); 47 | 48 | Optional neovimException = NeovimException.parseError(value); 49 | assertThat(neovimException.isPresent(), is(true)); 50 | assertThat(neovimException.get().getErrorCode(), is(FAILED_ERROR_CODE)); 51 | assertThat(neovimException.get().getMessage(), startsWith(FAILED_ERROR_MESSAGE_START)); 52 | } 53 | 54 | private JsonNode pack(Object... objects) throws JsonProcessingException { 55 | return MAPPER.convertValue(objects, JsonNode.class); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/com/neovim/msgpack/NotificationTest.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.runners.MockitoJUnitRunner; 8 | import org.msgpack.core.MessagePack; 9 | import org.msgpack.core.MessageUnpacker; 10 | 11 | import java.io.IOException; 12 | 13 | import static org.hamcrest.Matchers.is; 14 | import static org.junit.Assert.assertThat; 15 | 16 | @RunWith(MockitoJUnitRunner.class) 17 | public class NotificationTest { 18 | private static final String METHOD = "method"; 19 | private static final Integer ARG1 = 1; 20 | private static final String ARG2 = "2"; 21 | 22 | ObjectMapper objectMapper; 23 | 24 | @Before 25 | public void setUp() throws Exception { 26 | objectMapper = MessagePackRPC.defaultObjectMapper(); 27 | } 28 | 29 | @Test 30 | public void objectMapper_writeValue_serializesToMessagePackArray_noArgs() throws IOException { 31 | byte[] serialized = objectMapper.writeValueAsBytes(new Notification(METHOD)); 32 | MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(serialized); 33 | 34 | // Make sure value sent is [ Packet.NOTIFICATION_ID, METHOD, [ ]] 35 | assertThat(unpacker.unpackArrayHeader(), is(3)); 36 | assertThat(unpacker.unpackInt(), is(Packet.NOTIFICATION_ID)); 37 | assertThat(unpacker.unpackString(), is(METHOD)); 38 | assertThat(unpacker.unpackArrayHeader(), is(0)); 39 | assertThat(unpacker.hasNext(), is(false)); 40 | } 41 | 42 | @Test 43 | public void objectMapper_writeValue_serializesToMessagePackArray_oneArg() throws IOException { 44 | byte[] serialized = objectMapper.writeValueAsBytes(new Notification(METHOD, ARG1)); 45 | MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(serialized); 46 | 47 | // Make sure value sent is [ Packet.NOTIFICATION_ID, METHOD, [ ARG1 ]] 48 | assertThat(unpacker.unpackArrayHeader(), is(3)); 49 | assertThat(unpacker.unpackInt(), is(Packet.NOTIFICATION_ID)); 50 | assertThat(unpacker.unpackString(), is(METHOD)); 51 | assertThat(unpacker.unpackArrayHeader(), is(1)); 52 | assertThat(unpacker.unpackInt(), is(ARG1)); 53 | assertThat(unpacker.hasNext(), is(false)); 54 | } 55 | 56 | @Test 57 | public void objectMapper_writeValue_serializesToMessagePackArray_twoArgs() throws IOException { 58 | byte[] serialized = objectMapper.writeValueAsBytes(new Notification(METHOD, ARG1, ARG2)); 59 | MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(serialized); 60 | 61 | // Make sure value sent is [ Packet.NOTIFICATION_ID, METHOD, [ ARG1 ]] 62 | assertThat(unpacker.unpackArrayHeader(), is(3)); 63 | assertThat(unpacker.unpackInt(), is(Packet.NOTIFICATION_ID)); 64 | assertThat(unpacker.unpackString(), is(METHOD)); 65 | assertThat(unpacker.unpackArrayHeader(), is(2)); 66 | assertThat(unpacker.unpackInt(), is(ARG1)); 67 | assertThat(unpacker.unpackString(), is(ARG2)); 68 | assertThat(unpacker.hasNext(), is(false)); 69 | } 70 | } -------------------------------------------------------------------------------- /src/test/java/com/neovim/msgpack/RequestCallbackTest.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.core.JsonParser; 5 | import com.fasterxml.jackson.databind.JavaType; 6 | import com.fasterxml.jackson.databind.JsonNode; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import org.junit.Before; 9 | import org.junit.Rule; 10 | import org.junit.Test; 11 | import org.junit.rules.Timeout; 12 | import org.junit.runner.RunWith; 13 | import org.mockito.Mock; 14 | import org.mockito.runners.MockitoJUnitRunner; 15 | import org.msgpack.value.ImmutableValue; 16 | 17 | import java.io.IOException; 18 | import java.util.concurrent.ExecutionException; 19 | 20 | import static junit.framework.TestCase.fail; 21 | import static org.hamcrest.core.Is.is; 22 | import static org.junit.Assert.assertThat; 23 | import static org.mockito.Mockito.verify; 24 | import static org.mockito.Mockito.when; 25 | 26 | @RunWith(MockitoJUnitRunner.class) 27 | public class RequestCallbackTest { 28 | 29 | RequestCallback requestCallback; 30 | 31 | @Mock JsonNode data; 32 | @Mock JsonParser jsonParser; 33 | @Mock JavaType type; 34 | @Mock Object result; 35 | @Mock NeovimException neovimException; 36 | @Mock ObjectMapper objectMapper; 37 | 38 | @Rule public Timeout globalTimeout = new Timeout(10000); 39 | 40 | @Before 41 | public void setUp() throws Exception { 42 | when(data.traverse()).thenReturn(jsonParser); 43 | when(objectMapper.readValue(jsonParser, type)).thenReturn(result); 44 | 45 | requestCallback = new RequestCallback<>(type); 46 | } 47 | 48 | @Test 49 | public void setResult_type() throws IOException { 50 | RequestCallback requestCallback = new RequestCallback<>(type); 51 | requestCallback.setResult(objectMapper, data); 52 | 53 | verify(objectMapper).readValue(jsonParser, type); 54 | } 55 | 56 | @Test 57 | public void testSetError() throws Exception { 58 | requestCallback.setError(neovimException); 59 | 60 | assertThat(requestCallback.getCompletableFuture().isCompletedExceptionally(), is(true)); 61 | } 62 | 63 | @Test 64 | public void isDone_noResultOrException_false() throws Exception { 65 | assertThat(requestCallback.getCompletableFuture().isDone(), is(false)); 66 | } 67 | 68 | @Test 69 | public void isDone_hasException_true() { 70 | requestCallback.setError(neovimException); 71 | 72 | assertThat(requestCallback.getCompletableFuture().isDone(), is(true)); 73 | } 74 | 75 | @Test 76 | public void isDone_hasResult_true() throws IOException { 77 | requestCallback.setResult(objectMapper, data); 78 | 79 | assertThat(requestCallback.getCompletableFuture().isDone(), is(true)); 80 | } 81 | 82 | @Test 83 | public void get_rethrowError() throws ExecutionException, InterruptedException { 84 | requestCallback.setError(neovimException); 85 | 86 | try { 87 | requestCallback.getCompletableFuture().get(); 88 | fail(); 89 | } catch (ExecutionException expected) { 90 | assertThat(expected.getCause(), is(neovimException)); 91 | } 92 | } 93 | 94 | @Test 95 | public void get_returnsSetResult() throws Exception { 96 | requestCallback.setResult(objectMapper, data); 97 | 98 | assertThat(requestCallback.getCompletableFuture().get(), is(result)); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/test/java/com/neovim/msgpack/RequestIdGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | import com.google.common.primitives.UnsignedInteger; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.junit.runners.JUnit4; 7 | 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.junit.Assert.assertThat; 10 | 11 | @RunWith(JUnit4.class) 12 | public class RequestIdGeneratorTest { 13 | @Test 14 | public void nextId_twoAdjacentResultsNextToEachOther() { 15 | RequestIdGenerator id = new RequestIdGenerator(); 16 | assertThat(id.nextId() + 1, is(id.nextId())); 17 | } 18 | 19 | @Test 20 | public void nextId_unsignedWrapToZero() { 21 | RequestIdGenerator id = new RequestIdGenerator(-1); 22 | assertThat(id.nextId(), is(UnsignedInteger.MAX_VALUE.longValue())); 23 | assertThat(id.nextId(), is(0L)); 24 | } 25 | 26 | @Test 27 | public void nextId_unsignedForNegative() { 28 | RequestIdGenerator id = new RequestIdGenerator(Integer.MIN_VALUE); 29 | assertThat(id.nextId(), is(((long) Integer.MAX_VALUE) + 1L)); 30 | } 31 | } -------------------------------------------------------------------------------- /src/test/java/com/neovim/msgpack/RequestTest.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.runners.MockitoJUnitRunner; 8 | import org.msgpack.core.MessagePack; 9 | import org.msgpack.core.MessageUnpacker; 10 | 11 | import java.io.IOException; 12 | 13 | import static org.hamcrest.MatcherAssert.assertThat; 14 | import static org.hamcrest.Matchers.is; 15 | 16 | @RunWith(MockitoJUnitRunner.class) 17 | public class RequestTest { 18 | private static final long ID = 42L; 19 | private static final String METHOD = "method"; 20 | private static final Integer ARG1 = 1; 21 | private static final String ARG2 = "2"; 22 | 23 | ObjectMapper objectMapper; 24 | 25 | @Before 26 | public void setUp() throws Exception { 27 | objectMapper = MessagePackRPC.defaultObjectMapper(); 28 | } 29 | 30 | @Test 31 | public void objectMapper_writeValue_serializesToMessagePackArray_noArgs() throws IOException { 32 | Request request = new Request(METHOD); 33 | request.setRequestId(ID); 34 | byte[] serialized = objectMapper.writeValueAsBytes(request); 35 | MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(serialized); 36 | 37 | // Make sure value sent is [ Packet.REQUEST_ID, ID, METHOD, [ ]] 38 | assertThat(unpacker.unpackArrayHeader(), is(4)); 39 | assertThat(unpacker.unpackInt(), is(Packet.REQUEST_ID)); 40 | assertThat(unpacker.unpackLong(), is(ID)); 41 | assertThat(unpacker.unpackString(), is(METHOD)); 42 | assertThat(unpacker.unpackArrayHeader(), is(0)); 43 | assertThat(unpacker.hasNext(), is(false)); 44 | } 45 | 46 | @Test 47 | public void objectMapper_writeValue_serializesToMessagePackArray_oneArg() throws IOException { 48 | Request request = new Request(METHOD, ARG1); 49 | request.setRequestId(ID); 50 | byte[] serialized = objectMapper.writeValueAsBytes(request); 51 | MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(serialized); 52 | 53 | // Make sure value sent is [ Packet.REQUEST_ID, ID, METHOD, [ ARG1 ]] 54 | assertThat(unpacker.unpackArrayHeader(), is(4)); 55 | assertThat(unpacker.unpackInt(), is(Packet.REQUEST_ID)); 56 | assertThat(unpacker.unpackLong(), is(ID)); 57 | assertThat(unpacker.unpackString(), is(METHOD)); 58 | assertThat(unpacker.unpackArrayHeader(), is(1)); 59 | assertThat(unpacker.unpackInt(), is(ARG1)); 60 | assertThat(unpacker.hasNext(), is(false)); 61 | } 62 | 63 | @Test 64 | public void objectMapper_writeValue_serializesToMessagePackArray_twoArgs() throws IOException { 65 | Request request = new Request(METHOD, ARG1, ARG2); 66 | request.setRequestId(ID); 67 | byte[] serialized = objectMapper.writeValueAsBytes(request); 68 | MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(serialized); 69 | 70 | // Make sure value sent is [ Packet.REQUEST_ID, ID, METHOD, [ ARG1, ARG2 ]] 71 | assertThat(unpacker.unpackArrayHeader(), is(4)); 72 | assertThat(unpacker.unpackInt(), is(Packet.REQUEST_ID)); 73 | assertThat(unpacker.unpackLong(), is(ID)); 74 | assertThat(unpacker.unpackString(), is(METHOD)); 75 | assertThat(unpacker.unpackArrayHeader(), is(2)); 76 | assertThat(unpacker.unpackInt(), is(ARG1)); 77 | assertThat(unpacker.unpackString(), is(ARG2)); 78 | assertThat(unpacker.hasNext(), is(false)); 79 | } 80 | } -------------------------------------------------------------------------------- /src/test/java/com/neovim/msgpack/ResponseTest.java: -------------------------------------------------------------------------------- 1 | package com.neovim.msgpack; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.runners.MockitoJUnitRunner; 8 | import org.msgpack.core.MessagePack; 9 | import org.msgpack.core.MessageUnpacker; 10 | 11 | import java.io.IOException; 12 | 13 | import static org.hamcrest.MatcherAssert.assertThat; 14 | import static org.hamcrest.Matchers.is; 15 | 16 | @RunWith(MockitoJUnitRunner.class) 17 | public class ResponseTest { 18 | private static final long ID = 42L; 19 | private static final String METHOD = "method"; 20 | private static final String GOOD_RESULT = "result"; 21 | private static final NeovimException ERROR_RESULT = new NeovimException(0, "eh"); 22 | 23 | ObjectMapper objectMapper; 24 | 25 | @Before 26 | public void setUp() throws Exception { 27 | objectMapper = MessagePackRPC.defaultObjectMapper(); 28 | } 29 | 30 | @Test 31 | public void objectMapper_writeValue_serializesToMessagePackArray_goodResult() throws IOException { 32 | Response response = new Response(ID, GOOD_RESULT); 33 | byte[] serialized = objectMapper.writeValueAsBytes(response); 34 | MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(serialized); 35 | 36 | // Make sure value sent is [ Packet.RESPONSE_ID, ID, null, GOOD_RESULT] 37 | assertThat(unpacker.unpackArrayHeader(), is(4)); 38 | assertThat(unpacker.unpackInt(), is(Packet.RESPONSE_ID)); 39 | assertThat(unpacker.unpackLong(), is(ID)); 40 | unpacker.unpackNil(); 41 | assertThat(unpacker.unpackString(), is(GOOD_RESULT)); 42 | assertThat(unpacker.hasNext(), is(false)); 43 | } 44 | 45 | @Test 46 | public void objectMapper_writeValue_serializesToMessagePackArray_exception() throws IOException { 47 | Response response = new Response(ID, ERROR_RESULT); 48 | byte[] serialized = objectMapper.writeValueAsBytes(response); 49 | MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(serialized); 50 | System.out.println(unpacker.unpackValue().toString()); 51 | unpacker = MessagePack.newDefaultUnpacker(serialized); 52 | 53 | // Make sure value sent is [ Packet.RESPONSE_ID, ID, null, GOOD_RESULT] 54 | assertThat(unpacker.unpackArrayHeader(), is(4)); 55 | assertThat(unpacker.unpackInt(), is(Packet.RESPONSE_ID)); 56 | assertThat(unpacker.unpackLong(), is(ID)); 57 | assertThat(unpacker.unpackArrayHeader(), is(2)); 58 | assertThat(unpacker.unpackLong(), is(ERROR_RESULT.getErrorCode())); 59 | assertThat(unpacker.unpackString(), is(ERROR_RESULT.getMessage())); 60 | unpacker.unpackNil(); 61 | assertThat(unpacker.hasNext(), is(false)); 62 | } 63 | 64 | } 65 | --------------------------------------------------------------------------------