diff --git a/.classpath b/.classpath
index 381bcf0..0a893cb 100644
--- a/.classpath
+++ b/.classpath
@@ -10,5 +10,9 @@
+
+
+
+
diff --git a/build.xml b/build.xml
index 76fd29a..e1f45ee 100644
--- a/build.xml
+++ b/build.xml
@@ -14,7 +14,7 @@
-
+
diff --git a/required_libraries/CTRE_Phoenix-sources.jar b/required_libraries/CTRE_Phoenix-sources.jar
new file mode 100644
index 0000000..12cb27d
Binary files /dev/null and b/required_libraries/CTRE_Phoenix-sources.jar differ
diff --git a/required_libraries/CTRE_Phoenix.jar b/required_libraries/CTRE_Phoenix.jar
new file mode 100644
index 0000000..0c79a61
Binary files /dev/null and b/required_libraries/CTRE_Phoenix.jar differ
diff --git a/required_libraries/annotations-12.0.jar b/required_libraries/annotations-12.0.jar
new file mode 100644
index 0000000..7f8b362
Binary files /dev/null and b/required_libraries/annotations-12.0.jar differ
diff --git a/required_libraries/jackson-annotations-2.9.6.jar b/required_libraries/jackson-annotations-2.9.6.jar
new file mode 100644
index 0000000..51412a5
Binary files /dev/null and b/required_libraries/jackson-annotations-2.9.6.jar differ
diff --git a/required_libraries/jackson-core-2.9.6.jar b/required_libraries/jackson-core-2.9.6.jar
new file mode 100644
index 0000000..09e7dd2
Binary files /dev/null and b/required_libraries/jackson-core-2.9.6.jar differ
diff --git a/required_libraries/jackson-databind-2.9.6.jar b/required_libraries/jackson-databind-2.9.6.jar
new file mode 100644
index 0000000..e8eb658
Binary files /dev/null and b/required_libraries/jackson-databind-2.9.6.jar differ
diff --git a/required_libraries/libCTRE_PhoenixCCI.so b/required_libraries/libCTRE_PhoenixCCI.so
new file mode 100644
index 0000000..3303809
Binary files /dev/null and b/required_libraries/libCTRE_PhoenixCCI.so differ
diff --git a/required_libraries/navx_frc.jar b/required_libraries/navx_frc.jar
new file mode 100644
index 0000000..ea595d7
Binary files /dev/null and b/required_libraries/navx_frc.jar differ
diff --git a/src/com/github/arteam/simplejsonrpc/client/JsonRpcClient.java b/src/com/github/arteam/simplejsonrpc/client/JsonRpcClient.java
new file mode 100644
index 0000000..7bb00f7
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/JsonRpcClient.java
@@ -0,0 +1,152 @@
+package com.github.arteam.simplejsonrpc.client;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.arteam.simplejsonrpc.client.builder.BatchRequestBuilder;
+import com.github.arteam.simplejsonrpc.client.builder.NotificationRequestBuilder;
+import com.github.arteam.simplejsonrpc.client.builder.ObjectApiBuilder;
+import com.github.arteam.simplejsonrpc.client.builder.RequestBuilder;
+import com.github.arteam.simplejsonrpc.client.generator.IdGenerator;
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.reflect.Proxy;
+
+/**
+ * Date: 8/9/14
+ * Time: 8:58 PM
+ *
+ * JSON-RPC client. Represents a factory for a fluent client API {@link com.github.arteam.simplejsonrpc.client.builder.RequestBuilder}.
+ * It's parametrized by {@link Transport} and Jackson {@link ObjectMapper}
+ *
+ * @author Artem Prigoda
+ */
+public class JsonRpcClient {
+
+ /**
+ * Transport for performing JSON-RPC requests and returning responses
+ */
+ @NotNull
+ private Transport transport;
+
+ /**
+ * JSON mapper for conversion between JSON and Java types
+ */
+ @NotNull
+ private ObjectMapper mapper;
+
+ /**
+ * Constructs a new JSON-RPC client with a specified transport
+ *
+ * @param transport transport implementation
+ */
+ public JsonRpcClient(@NotNull Transport transport) {
+ this(transport, new ObjectMapper());
+ }
+
+ /**
+ * Constructs a new JSON-RPC client with a specified transport and user-definder JSON mapper
+ *
+ * @param transport transport implementation
+ * @param mapper JSON mapper
+ */
+ public JsonRpcClient(@NotNull Transport transport, @NotNull ObjectMapper mapper) {
+ this.transport = transport;
+ this.mapper = mapper;
+ }
+
+ /**
+ * Creates a builder of a JSON-RPC request in initial state
+ *
+ * @return request builder
+ */
+ @NotNull
+ public RequestBuilder createRequest() {
+ return new RequestBuilder(transport, mapper);
+ }
+
+ /**
+ * Creates a builder of a JSON-RPC notification request in initial state
+ *
+ * @return notification request builder
+ */
+ @NotNull
+ public NotificationRequestBuilder createNotification() {
+ return new NotificationRequestBuilder(transport, mapper);
+ }
+
+ /**
+ * Creates a builder of a JSON-RPC batch request in initial state
+ *
+ * @return batch request builder
+ */
+ @NotNull
+ public BatchRequestBuilder, ?> createBatchRequest() {
+ return new BatchRequestBuilder(transport, mapper);
+ }
+
+ /**
+ * Creates a new proxy for accessing a remote JSON-RPC service through an interface
+ *
+ * @param clazz interface metadata
+ * @param interface type
+ * @return a new proxy
+ */
+ @SuppressWarnings("unchecked")
+ @NotNull
+ public T onDemand(@NotNull Class clazz) {
+ return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{clazz},
+ new ObjectApiBuilder(clazz, transport, mapper, null, null));
+ }
+
+ /**
+ * Creates a new proxy for accessing a remote JSON-RPC service through an interface
+ * with a custom id generator that overrides the interface generator.
+ *
+ * @param clazz interface metadata
+ * @param idGenerator custom id generator
+ * @param interface type
+ * @return a new proxy
+ */
+ @SuppressWarnings("unchecked")
+ @NotNull
+ public T onDemand(@NotNull Class clazz, @NotNull IdGenerator> idGenerator) {
+ return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{clazz},
+ new ObjectApiBuilder(clazz, transport, mapper, null, idGenerator));
+ }
+
+ /**
+ * Creates a new proxy for accessing a remote JSON-RPC service through an interface
+ * with a custom type of request params.
+ * It applies for all methods and overrides interface and method level settings.
+ *
+ * @param clazz interface metadata
+ * @param paramsType custom type of request params
+ * @param interface type
+ * @return a new proxy
+ */
+ @SuppressWarnings("unchecked")
+ @NotNull
+ public T onDemand(@NotNull Class clazz, @NotNull ParamsType paramsType) {
+ return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{clazz},
+ new ObjectApiBuilder(clazz, transport, mapper, paramsType, null));
+ }
+
+ /**
+ * Creates a new proxy for accessing a remote JSON-RPC service through an interface
+ * with a custom id generator and custom type of request params.
+ * The generator overrides the interface generator.
+ * The type applies for all methods and overrides interface and method level settings.
+ *
+ * @param clazz interface metadata
+ * @param idGenerator custom id generator
+ * @param paramsType custom type of request params
+ * @param interface type
+ * @return a new proxy
+ */
+ @SuppressWarnings("unchecked")
+ @NotNull
+ public T onDemand(Class clazz, @NotNull ParamsType paramsType, @NotNull IdGenerator> idGenerator) {
+ return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{clazz},
+ new ObjectApiBuilder(clazz, transport, mapper, paramsType, idGenerator));
+ }
+
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/JsonRpcId.java b/src/com/github/arteam/simplejsonrpc/client/JsonRpcId.java
new file mode 100644
index 0000000..e31fd17
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/JsonRpcId.java
@@ -0,0 +1,21 @@
+package com.github.arteam.simplejsonrpc.client;
+
+import com.github.arteam.simplejsonrpc.client.generator.IdGenerator;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Date: 24.08.14
+ * Time: 18:14
+ *
+ * @author Artem Prigoda
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JsonRpcId {
+
+ Class extends IdGenerator>> value();
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/JsonRpcParams.java b/src/com/github/arteam/simplejsonrpc/client/JsonRpcParams.java
new file mode 100644
index 0000000..2afa08b
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/JsonRpcParams.java
@@ -0,0 +1,17 @@
+package com.github.arteam.simplejsonrpc.client;
+
+import java.lang.annotation.*;
+
+/**
+ * Date: 11/4/14
+ * Time: 10:45 PM
+ *
+ * @author Artem Prigoda
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Documented
+public @interface JsonRpcParams {
+
+ ParamsType value() default ParamsType.MAP;
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/ParamsType.java b/src/com/github/arteam/simplejsonrpc/client/ParamsType.java
new file mode 100644
index 0000000..758389e
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/ParamsType.java
@@ -0,0 +1,12 @@
+package com.github.arteam.simplejsonrpc.client;
+
+/**
+ * Date: 11/4/14
+ * Time: 10:14 PM
+ * Style of JSON-RPC parameters representation (map or array)
+ *
+ * @author Artem Prigoda
+ */
+public enum ParamsType {
+ MAP, ARRAY
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/Transport.java b/src/com/github/arteam/simplejsonrpc/client/Transport.java
new file mode 100644
index 0000000..aa22343
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/Transport.java
@@ -0,0 +1,26 @@
+package com.github.arteam.simplejsonrpc.client;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+
+/**
+ * Date: 8/9/14
+ * Time: 8:52 PM
+ *
+ * Abstract transport for JSON-RPC communication
+ *
+ * @author Artem Prigoda
+ */
+public interface Transport {
+
+ /**
+ * Passes a JSON-RPC request in a text form to a backend and
+ * returns a JSON-RPC response in a text form as well
+ *
+ * @param request JSON-RPC request as a string
+ * @return JSON-RPC response as a string
+ */
+ @NotNull
+ public String pass(@NotNull String request) throws IOException;
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/builder/AbstractBuilder.java b/src/com/github/arteam/simplejsonrpc/client/builder/AbstractBuilder.java
new file mode 100644
index 0000000..035a4b3
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/builder/AbstractBuilder.java
@@ -0,0 +1,103 @@
+package com.github.arteam.simplejsonrpc.client.builder;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.node.ValueNode;
+import com.github.arteam.simplejsonrpc.client.Transport;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Date: 10/12/14
+ * Time: 6:48 PM
+ * Abstract builder for JSON-RPC requests
+ *
+ * @author Artem Prigoda
+ */
+public class AbstractBuilder {
+
+ // Protocol constants
+ protected static final String VERSION_2_0 = "2.0";
+ protected static final String RESULT = "result";
+ protected static final String ERROR = "error";
+ protected static final String JSONRPC = "jsonrpc";
+ protected static final String ID = "id";
+ protected static final String METHOD = "method";
+ protected static final String PARAMS = "params";
+
+ /**
+ * Transport for performing a text request and returning a text response
+ */
+ @NotNull
+ protected final Transport transport;
+
+ /**
+ * Jackson mapper for JSON processing
+ */
+ @NotNull
+ protected final ObjectMapper mapper;
+
+ public AbstractBuilder(@NotNull Transport transport, @NotNull ObjectMapper mapper) {
+ this.transport = transport;
+ this.mapper = mapper;
+ }
+
+ /**
+ * Builds request params as a JSON array
+ *
+ * @param values request params
+ * @return a new JSON array
+ */
+ @NotNull
+ protected ArrayNode arrayParams(@NotNull Object[] values) {
+ ArrayNode newArrayParams = mapper.createArrayNode();
+ for (Object value : values) {
+ newArrayParams.add(mapper.valueToTree(value));
+ }
+ return newArrayParams;
+ }
+
+ /**
+ * Builds request params as a JSON object
+ *
+ * @param params request params
+ * @return a new JSON object
+ */
+ @NotNull
+ protected ObjectNode objectParams(@NotNull Map params) {
+ ObjectNode objectNode = mapper.createObjectNode();
+ for (String key : params.keySet()) {
+ objectNode.set(key, mapper.valueToTree(params.get(key)));
+ }
+ return objectNode;
+ }
+
+ /**
+ * Creates a new JSON-RPC request as a JSON object
+ *
+ * @param id request id
+ * @param method request method
+ * @param params request params
+ * @return a new request as a JSON object
+ */
+ @NotNull
+ protected ObjectNode request(@NotNull ValueNode id, @NotNull String method,
+ @NotNull JsonNode params) {
+ if (method.isEmpty()) {
+ throw new IllegalArgumentException("Method is not set");
+ }
+ ObjectNode requestNode = mapper.createObjectNode();
+ requestNode.put(JSONRPC, VERSION_2_0);
+ requestNode.put(METHOD, method);
+ requestNode.set(PARAMS, params);
+ if (!id.isNull()) {
+ requestNode.set(ID, id);
+ }
+ return requestNode;
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/builder/BatchRequestBuilder.java b/src/com/github/arteam/simplejsonrpc/client/builder/BatchRequestBuilder.java
new file mode 100644
index 0000000..bdc1e9c
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/builder/BatchRequestBuilder.java
@@ -0,0 +1,620 @@
+package com.github.arteam.simplejsonrpc.client.builder;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.*;
+import com.fasterxml.jackson.databind.type.SimpleType;
+import com.github.arteam.simplejsonrpc.client.Transport;
+import com.github.arteam.simplejsonrpc.client.exception.JsonRpcBatchException;
+import com.github.arteam.simplejsonrpc.core.domain.ErrorMessage;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Date: 10/12/14
+ * Time: 6:23 PM
+ *
+ * Fluent builder of batch JSON-RPC requests.
+ *
+ * It provides facility to create a list of requests, set expected response types, execute the JSON-RPC request
+ * and process the JSON-RPC response.
+ *
+ * Return type is a map of responses (Java objects) by request ids. In cases of errors it throws
+ * {@code JsonRpcBatchException} with detailed status of success and failed requests.
+ *
+ * It delegates JSON processing to Jackson {@link ObjectMapper} and actual request performing to {@link com.github.arteam.simplejsonrpc.client.Transport}.
+ *
+ * The basic pattern is following:
+ *
+ * Map result = client.createBatchRequest()
+ * .add("43121", "findByInitials", "Steven", "Stamkos")
+ * .add("43122", "findByInitials", "Jack", "Allen")
+ * .keysType(String.class)
+ * .returnType(Player.class)
+ * .execute();
+ *
+ * @author Artem Prigoda
+ */
+public class BatchRequestBuilder extends AbstractBuilder {
+
+ /**
+ * List of requests
+ */
+ @NotNull
+ private final List requests;
+
+ /**
+ * Map of expected return types by request ids
+ */
+ @NotNull
+ private final Map returnTypes;
+
+ /**
+ * Type of request ids
+ */
+ @Nullable
+ private final Class keysType;
+
+ /**
+ * Expected return type for all requests
+ *
+ * This property works exclusively with {@code returnTypes}. Only one of them should be set.
+ */
+ @Nullable
+ private final JavaType returnType;
+
+ /**
+ * Creates a new batch request builder in an initial state
+ *
+ * @param transport transport for request performing
+ * @param mapper mapper for JSON processing
+ */
+ public BatchRequestBuilder(@NotNull Transport transport, @NotNull ObjectMapper mapper) {
+ this(transport, mapper, new ArrayList(), new HashMap(), null, null);
+ }
+
+ /**
+ * Creates a new batch request builder as a part of a chain
+ *
+ * @param transport transport for request performing
+ * @param mapper mapper for JSON processing
+ * @param requests new requests
+ * @param returnTypes new return types
+ * @param keysType new key type
+ * @param returnType new values type
+ */
+ public BatchRequestBuilder(@NotNull Transport transport, @NotNull ObjectMapper mapper,
+ @NotNull List requests, @NotNull Map returnTypes,
+ @Nullable Class keysType, @Nullable JavaType returnType) {
+ super(transport, mapper);
+ this.requests = requests;
+ this.returnTypes = returnTypes;
+ this.keysType = keysType;
+ this.returnType = returnType;
+ }
+
+ /**
+ * Adds a new request without specifying a return type
+ *
+ * @param id request id as a long value
+ * @param method request method
+ * @param params request params as an array
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(long id, @NotNull String method, @NotNull Object... params) {
+ requests.add(request(new LongNode(id), method, arrayParams(params)));
+ return this;
+ }
+
+ /**
+ * Adds a new request without specifying a return type
+ *
+ * @param id request id as an int value
+ * @param method request method
+ * @param params request params as an array
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(int id, @NotNull String method, @NotNull Object... params) {
+ requests.add(request(new IntNode(id), method, arrayParams(params)));
+ return this;
+ }
+
+ /**
+ * Adds a new request without specifying a return type
+ *
+ * @param id request id as a text value
+ * @param method request method
+ * @param params request params as an array
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(String id, @NotNull String method, @NotNull Object... params) {
+ requests.add(request(new TextNode(id), method, arrayParams(params)));
+ return this;
+ }
+
+ /**
+ * Adds a new notification request without specifying a return type
+ *
+ * @param method request method
+ * @param params request params as an array
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(@NotNull String method, @NotNull Object... params) {
+ requests.add(request(NullNode.instance, method, arrayParams(params)));
+ return this;
+ }
+
+ /**
+ * Adds a new request without specifying a return type
+ *
+ * @param id request id as a long value
+ * @param method request method
+ * @param params request params as a map of parameter names to values
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(long id, @NotNull String method, @NotNull Map params) {
+ requests.add(request(new LongNode(id), method, objectParams(params)));
+ return this;
+ }
+
+ /**
+ * Adds a new request without specifying a return type
+ *
+ * @param id request id as an int value
+ * @param method request method
+ * @param params request params as a map of parameter names to values
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(int id, @NotNull String method, @NotNull Map params) {
+ requests.add(request(new IntNode(id), method, objectParams(params)));
+ return this;
+ }
+
+ /**
+ * Adds a new request without specifying a return type
+ *
+ * @param id request id as a text value
+ * @param method request method
+ * @param params request params as a map of parameter names to values
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(String id, @NotNull String method, @NotNull Map params) {
+ requests.add(request(new TextNode(id), method, objectParams(params)));
+ return this;
+ }
+
+ /**
+ * Adds a new notification request without specifying a return type
+ *
+ * @param method request method
+ * @param params request params as an array
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(@NotNull String method, @NotNull Map params) {
+ requests.add(request(NullNode.instance, method, objectParams(params)));
+ return this;
+ }
+
+ /**
+ * Adds a new request with a return type
+ *
+ * @param id request id as a long value
+ * @param method request method
+ * @param params request params as an array
+ * @param responseType expected response type
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(long id, @NotNull String method, @NotNull Object[] params,
+ @NotNull Class> responseType) {
+ return add(id, method, params).returnType(id, responseType);
+ }
+
+ /**
+ * Adds a new request with a return type
+ *
+ * @param id request id as an int value
+ * @param method request method
+ * @param params request params as an array
+ * @param responseType expected response type
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(int id, @NotNull String method, @NotNull Object[] params,
+ @NotNull Class> responseType) {
+ return add(id, method, params).returnType(id, responseType);
+ }
+
+ /**
+ * Adds a new request with a return type
+ *
+ * @param id request id as a text value
+ * @param method request method
+ * @param params request params as an array
+ * @param responseType expected response type
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(String id, @NotNull String method, @NotNull Object[] params,
+ @NotNull Class> responseType) {
+ return add(id, method, params).returnType(id, responseType);
+ }
+
+ /**
+ * Adds a new request with a return type
+ *
+ * @param id request id as a long value
+ * @param method request method
+ * @param params request params as a map of parameter names to values
+ * @param responseType expected response type
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(long id, @NotNull String method, @NotNull Map params,
+ @NotNull Class> responseType) {
+ return add(id, method, params).returnType(id, responseType);
+ }
+
+ /**
+ * Adds a new request with a return type
+ *
+ * @param id request id as an int value
+ * @param method request method
+ * @param params request params as a map of parameter names to values
+ * @param responseType expected response type
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(int id, @NotNull String method, @NotNull Map params,
+ @NotNull Class> responseType) {
+ return add(id, method, params).returnType(id, responseType);
+ }
+
+ /**
+ * Adds a new request with a return type
+ *
+ * @param id request id as a text value
+ * @param method request method
+ * @param params request params as a map of parameter names to values
+ * @param responseType expected response type
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(String id, @NotNull String method, @NotNull Map params,
+ @NotNull Class> responseType) {
+ return add(id, method, params).returnType(id, responseType);
+ }
+
+ /**
+ * Adds a new request with a complex return type
+ *
+ * @param id request id as a long value
+ * @param method request method
+ * @param params request params as an array
+ * @param typeReference expected complex response type
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(long id, @NotNull String method, @NotNull Object[] params,
+ @NotNull TypeReference> typeReference) {
+ return add(id, method, params).returnType(id, typeReference);
+ }
+
+ /**
+ * Adds a new request with a complex return type
+ *
+ * @param id request id as an int value
+ * @param method request method
+ * @param params request params as an array
+ * @param typeReference expected complex response type
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(int id, @NotNull String method, @NotNull Object[] params,
+ @NotNull TypeReference> typeReference) {
+ return add(id, method, params).returnType(id, typeReference);
+ }
+
+ /**
+ * Adds a new request with a complex return type
+ *
+ * @param id request id as a text value
+ * @param method request method
+ * @param params request params as an array
+ * @param typeReference expected complex response type
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(String id, @NotNull String method, @NotNull Object[] params,
+ @NotNull TypeReference> typeReference) {
+ return add(id, method, params).returnType(id, typeReference);
+ }
+
+ /**
+ * Adds a new request with a complex return type
+ *
+ * @param id request id as a long value
+ * @param method request method
+ * @param params request params as a map of parameter names to values
+ * @param typeReference expected complex response type
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(long id, @NotNull String method, @NotNull Map params,
+ @NotNull TypeReference> typeReference) {
+ return add(id, method, params).returnType(id, typeReference);
+ }
+
+ /**
+ * Adds a new request with a complex return type
+ *
+ * @param id request id as an int value
+ * @param method request method
+ * @param params request params as a map of parameter names to values
+ * @param typeReference expected complex response type
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(int id, @NotNull String method, @NotNull Map params,
+ @NotNull TypeReference> typeReference) {
+ return add(id, method, params).returnType(id, typeReference);
+ }
+
+ /**
+ * Adds a new request with a complex return type
+ *
+ * @param id request id as a text value
+ * @param method request method
+ * @param params request params as a map of parameter names to values
+ * @param typeReference expected complex response type
+ * @return the current builder
+ */
+ @NotNull
+ public BatchRequestBuilder add(String id, @NotNull String method, @NotNull Map params,
+ @NotNull TypeReference> typeReference) {
+ return add(id, method, params).returnType(id, typeReference);
+ }
+
+ /**
+ * Sets an expected return type for a request
+ *
+ * @param id request id
+ * @param responseType expected response type
+ * @return a new builder
+ */
+ private BatchRequestBuilder returnType(Object id, @NotNull Class> responseType) {
+ returnTypes.put(id, SimpleType.construct(responseType));
+ return this;
+ }
+
+ /**
+ * Sets an expected return type as a complex type for a request
+ *
+ * @param id request id
+ * @param typeReference expected response type as a complex type
+ * @return a new builder
+ */
+ private BatchRequestBuilder returnType(Object id, @NotNull TypeReference> typeReference) {
+ returnTypes.put(id, mapper.getTypeFactory().constructType(typeReference.getType()));
+ return this;
+ }
+
+ /**
+ * Sets type of request keys.
+ * The purpose of this method is providing static and runtime type safety of processing of batch responses
+ *
+ * @param keysClass type of keys
+ * @param type of keys
+ * @return a new builder
+ */
+ public BatchRequestBuilder keysType(@NotNull Class keysClass) {
+ return new BatchRequestBuilder(transport, mapper, requests, returnTypes, keysClass, returnType);
+ }
+
+ /**
+ * Sets an expected response type of requests.
+ * This method is preferred when requests have the same response type.
+ *
+ * @param valuesClass expected requests return type
+ * @param expected requests return type
+ * @return a new builder
+ */
+ public BatchRequestBuilder returnType(@NotNull Class valuesClass) {
+ return new BatchRequestBuilder(transport, mapper, requests, returnTypes, keysType,
+ SimpleType.construct(valuesClass));
+ }
+
+ /**
+ * Sets an expected complex response type of requests.
+ *
+ * @param tr expected complex requests return type
+ * @param expected requests return type
+ * @return a new builder
+ */
+ public BatchRequestBuilder returnType(@NotNull TypeReference tr) {
+ return new BatchRequestBuilder(transport, mapper, requests, returnTypes, keysType,
+ mapper.constructType(tr.getType()));
+ }
+
+ /**
+ * Validates, executes the request and process response
+ *
+ * @return map of responses by request ids
+ */
+ @NotNull
+ public Map execute() {
+ validateRequest();
+ String textResponse = executeRequest();
+ return processBatchResponse(textResponse);
+ }
+
+ /**
+ * Validates the request as a valid batch JSON-RPC request
+ */
+ private void validateRequest() {
+ if (requests.isEmpty()) {
+ throw new IllegalArgumentException("Requests are not set");
+ }
+
+ List> requestIds = requestIds();
+ if (returnType == null) {
+ for (Object id : requestIds) {
+ if (!returnTypes.containsKey(id)) {
+ throw new IllegalArgumentException("Return type isn't specified for " +
+ "request with id='" + id + "'");
+ }
+ }
+ } else if (!returnTypes.isEmpty()) {
+ throw new IllegalArgumentException("Common and detailed configurations of return types shouldn't be mixed");
+ }
+
+ for (Object id : requestIds) {
+ checkIdType(id);
+ }
+ }
+
+ /**
+ * Executes the request through the transport
+ *
+ * @return backend response as a string
+ */
+ @NotNull
+ private String executeRequest() {
+ try {
+ return transport.pass(mapper.writeValueAsString(requests));
+ } catch (IOException e) {
+ throw new IllegalStateException("I/O error during a request processing", e);
+ }
+ }
+
+ /**
+ * Processes JSON-RPC batch response
+ *
+ * @param textResponse response as a string
+ * @return map of responses (Java objects) by request ids
+ */
+ @NotNull
+ @SuppressWarnings("unchecked")
+ private Map processBatchResponse(@NotNull String textResponse) {
+ Map successes = new HashMap();
+ Map errors = new HashMap();
+ List> requestIds = requestIds();
+
+ try {
+ JsonNode jsonResponses = mapper.readTree(textResponse);
+ // If it's an empty response
+ if (jsonResponses.isTextual() && jsonResponses.asText().isEmpty() && requestIds.isEmpty()) {
+ return new HashMap();
+ }
+ // Not an array
+ if (jsonResponses.getNodeType() != JsonNodeType.ARRAY) {
+ throw new IllegalStateException("Expected array but was " + jsonResponses.getNodeType());
+ }
+
+ for (JsonNode responseNode : (ArrayNode) jsonResponses) {
+ processSingleResponse(responseNode, requestIds, successes, errors);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Unable parse a JSON response: " + textResponse, e);
+ }
+ if (!errors.isEmpty()) {
+ throw new JsonRpcBatchException("Errors happened during batch request processing", successes, errors);
+ }
+ return (Map) successes;
+ }
+
+ private void processSingleResponse(@NotNull JsonNode responseNode, @NotNull List> requestIds,
+ @NotNull Map successes,
+ @NotNull Map errors)
+ throws JsonProcessingException {
+ checkVersion(responseNode, responseNode.get(JSONRPC));
+
+ JsonNode result = responseNode.get(RESULT);
+ JsonNode error = responseNode.get(ERROR);
+ if (error == null && result == null) {
+ throw new IllegalStateException("Neither result or error is set in response: " + responseNode);
+ }
+
+ // Check id and convert it to long if necessary
+ Object idValue = nodeValue(responseNode.get(ID));
+ if (keysType == Long.class && idValue.getClass() == Integer.class) {
+ idValue = ((Integer) idValue).longValue();
+ }
+
+ if (!requestIds.contains(idValue)) {
+ throw new IllegalStateException("Unspecified id: '" + idValue + "' in response");
+ }
+
+ if (result != null) {
+ JavaType actualReturnType = returnType != null ? returnType : returnTypes.get(idValue);
+ successes.put(idValue, mapper.convertValue(result, actualReturnType));
+ } else {
+ // Process as an error
+ errors.put(idValue, mapper.treeToValue(error, ErrorMessage.class));
+ }
+ }
+
+ private void checkVersion(JsonNode responseNode, JsonNode version) {
+ if (version == null) {
+ throw new IllegalStateException("Not a JSON-RPC response: " + responseNode);
+ }
+ if (!version.asText().equals(VERSION_2_0)) {
+ throw new IllegalStateException("Bad protocol version in a response: " + responseNode);
+ }
+ }
+
+ private void checkIdType(@NotNull Object id) {
+ if (keysType != null && !keysType.equals(id.getClass())) {
+ throw new IllegalArgumentException("Id: '" + id + "' has wrong type: '" + id.getClass().getSimpleName() +
+ "'. Should be: '" + keysType.getSimpleName() + "'");
+ }
+ }
+
+ @NotNull
+ private List> requestIds() {
+ List ids = new ArrayList(requests.size());
+ for (ObjectNode request : requests) {
+ JsonNode id = request.get(ID);
+ if (id != null) {
+ ids.add(nodeValue(id));
+ }
+ }
+ return ids;
+ }
+
+ // Visible for tests
+ @NotNull
+ List getRequests() {
+ return requests;
+ }
+
+ @NotNull
+ private static Object nodeValue(@NotNull JsonNode id) {
+ if (id.isLong()) {
+ return id.longValue();
+ } else if (id.isInt()) {
+ return id.intValue();
+ } else if (id.isTextual()) {
+ return id.textValue();
+ }
+ throw new IllegalArgumentException("Wrong id=" + id);
+ }
+
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/builder/NotificationRequestBuilder.java b/src/com/github/arteam/simplejsonrpc/client/builder/NotificationRequestBuilder.java
new file mode 100644
index 0000000..40c9afb
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/builder/NotificationRequestBuilder.java
@@ -0,0 +1,86 @@
+package com.github.arteam.simplejsonrpc.client.builder;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.arteam.simplejsonrpc.client.Transport;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Date: 8/17/14
+ * Time: 11:09 PM
+ *
+ * Type-safe builder of JSON-RPC notification requests.
+ *
+ * It uses underlying {@link RequestBuilder} to build a request, but not permits setting a request id.
+ * Also it doesn't expect any response from the server, so there is no response parsing.
+ *
+ * @author Artem Prigoda
+ */
+public class NotificationRequestBuilder {
+
+ /**
+ * Delegated request builder
+ */
+ private RequestBuilder requestBuilder;
+
+ /**
+ * Creates a new notification request builder
+ *
+ * @param transport transport for request performing
+ * @param mapper mapper for JSON processing
+ */
+ public NotificationRequestBuilder(@NotNull Transport transport, @NotNull ObjectMapper mapper) {
+ requestBuilder = new RequestBuilder(transport, mapper);
+ }
+
+ /**
+ * Creates a new notification request builder as a chain of builders
+ *
+ * @param requestBuilder a new notification request builder
+ */
+ private NotificationRequestBuilder(RequestBuilder requestBuilder) {
+ this.requestBuilder = requestBuilder;
+ }
+
+ /**
+ * Sets a request method
+ *
+ * @param method a request method
+ * @return new builder
+ */
+ @NotNull
+ public NotificationRequestBuilder method(@NotNull String method) {
+ return new NotificationRequestBuilder(requestBuilder.method(method));
+ }
+
+ /**
+ * Adds a new parameter to current request parameters.
+ *
+ * @param name parameter name
+ * @param value parameter value
+ * @return new builder
+ */
+ @NotNull
+ public NotificationRequestBuilder param(@NotNull String name, @NotNull Object value) {
+ return new NotificationRequestBuilder(requestBuilder.param(name, value));
+ }
+
+ /**
+ * Sets request parameters to request parameters.
+ * Parameters are interpreted according to its positions.
+ *
+ * @param values array of parameters
+ * @return new builder
+ */
+ @NotNull
+ public NotificationRequestBuilder params(@NotNull Object... values) {
+ return new NotificationRequestBuilder(requestBuilder.params(values));
+ }
+
+ /**
+ * Execute a request through {@link Transport}
+ */
+ public void execute() {
+ requestBuilder.executeRequest();
+ }
+
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/builder/ObjectApiBuilder.java b/src/com/github/arteam/simplejsonrpc/client/builder/ObjectApiBuilder.java
new file mode 100644
index 0000000..4067b30
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/builder/ObjectApiBuilder.java
@@ -0,0 +1,160 @@
+package com.github.arteam.simplejsonrpc.client.builder;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.*;
+import com.github.arteam.simplejsonrpc.client.ParamsType;
+import com.github.arteam.simplejsonrpc.client.Transport;
+import com.github.arteam.simplejsonrpc.client.exception.JsonRpcException;
+import com.github.arteam.simplejsonrpc.client.generator.IdGenerator;
+import com.github.arteam.simplejsonrpc.client.metadata.ClassMetadata;
+import com.github.arteam.simplejsonrpc.client.metadata.MethodMetadata;
+import com.github.arteam.simplejsonrpc.client.metadata.ParameterMetadata;
+import com.github.arteam.simplejsonrpc.core.domain.ErrorMessage;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+
+/**
+ * Date: 24.08.14
+ * Time: 17:33
+ * Proxy for accessing a remote JSON-RPC service trough an interface.
+ *
+ * @author Artem Prigoda
+ */
+public class ObjectApiBuilder extends AbstractBuilder implements InvocationHandler {
+
+ @Nullable
+ private ParamsType userParamsType;
+
+ @Nullable
+ private IdGenerator userIdGenerator;
+
+ @NotNull
+ private ClassMetadata classMetadata;
+
+ /**
+ * Crate a new proxy for an interface
+ *
+ * @param clazz service interface
+ * @param transport transport abstraction
+ * @param mapper json mapper
+ * @param userParamsType custom type of request params
+ * @param userIdGenerator custom id generator
+ */
+ public ObjectApiBuilder(@NotNull Class> clazz, @NotNull Transport transport, @NotNull ObjectMapper mapper,
+ @Nullable ParamsType userParamsType, @Nullable IdGenerator userIdGenerator) {
+ super(transport, mapper);
+ this.classMetadata = Reflections.getClassMetadata(clazz);
+ this.userParamsType = userParamsType;
+ this.userIdGenerator = userIdGenerator;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ // Check that it's a JSON-RPC method
+ MethodMetadata methodMetadata = classMetadata.getMethods().get(method);
+ if (methodMetadata == null) {
+ throw new IllegalStateException("Method '" + method.getName() + "' is not JSON-RPC available");
+ }
+
+ // Get method name (annotation or the actual name), params and id generator
+ String methodName = methodMetadata.getName();
+ JsonNode params = getParams(methodMetadata, args, getParamsType(classMetadata, methodMetadata));
+ IdGenerator> idGenerator = userIdGenerator != null ? userIdGenerator : classMetadata.getIdGenerator();
+
+ // Construct a request
+ ValueNode id = new POJONode(idGenerator.generate());
+ String textResponse = execute(request(id, methodName, params));
+
+ // Parse a response
+ JsonNode responseNode = mapper.readTree(textResponse);
+ JsonNode result = responseNode.get(RESULT);
+ JsonNode error = responseNode.get(ERROR);
+ if (result != null) {
+ JavaType returnType = mapper.getTypeFactory().constructType(method.getGenericReturnType());
+ if (returnType.getRawClass() == void.class) {
+ return null;
+ }
+ return mapper.convertValue(result, returnType);
+ } else {
+ ErrorMessage errorMessage = mapper.treeToValue(error, ErrorMessage.class);
+ throw new JsonRpcException(errorMessage);
+ }
+ }
+
+ /**
+ * Get request params in a JSON representation (map or array)
+ */
+ @NotNull
+ private JsonNode getParams(@NotNull MethodMetadata method, @NotNull Object[] args,
+ @NotNull ParamsType paramsType) {
+ ObjectNode paramsAsMap = mapper.createObjectNode();
+ ArrayNode paramsAsArray = mapper.createArrayNode();
+ for (String paramName : method.getParams().keySet()) {
+ ParameterMetadata parameterMetadata = method.getParams().get(paramName);
+ int index = parameterMetadata.getIndex();
+ JsonNode jsonArg = mapper.valueToTree(args[index]);
+ if (jsonArg == null || jsonArg == NullNode.instance) {
+ if (parameterMetadata.isOptional()) {
+ if (paramsType == ParamsType.ARRAY) {
+ paramsAsArray.add(NullNode.instance);
+ }
+ } else {
+ throw new IllegalArgumentException("Parameter '" + paramName +
+ "' of method '" + method.getName() + "' is mandatory and can't be null");
+ }
+ } else {
+ if (paramsType == ParamsType.MAP) {
+ paramsAsMap.set(paramName, jsonArg);
+ } else if (paramsType == ParamsType.ARRAY) {
+ paramsAsArray.add(jsonArg);
+ }
+ }
+ }
+ return paramsType == ParamsType.MAP ? paramsAsMap : paramsAsArray;
+ }
+
+ /**
+ * Execute a request on a remote service and return a textual representation of a response
+ *
+ * @param request json representation of a request
+ * @return service response as a string
+ */
+ @NotNull
+ private String execute(@NotNull ObjectNode request) {
+ try {
+ return transport.pass(mapper.writeValueAsString(request));
+ } catch (JsonProcessingException e) {
+ throw new IllegalArgumentException("Unable convert " + request + " to JSON", e);
+ } catch (IOException e) {
+ throw new IllegalStateException("I/O error during request processing", e);
+ }
+ }
+
+ /**
+ * Get style of params for a request.
+ * It could be either on a method, class or user level. MAP is a fallback choice as default.
+ *
+ * @param classMetadata metadata of a service interface
+ * @param methodMetadata metadata of a method
+ * @return type of params
+ */
+ @NotNull
+ private ParamsType getParamsType(@NotNull ClassMetadata classMetadata, @NotNull MethodMetadata methodMetadata) {
+ if (userParamsType != null) {
+ return userParamsType;
+ } else if (methodMetadata.getParamsType() != null) {
+ return methodMetadata.getParamsType();
+ } else if (classMetadata.getParamsType() != null) {
+ return classMetadata.getParamsType();
+ }
+ return ParamsType.MAP;
+ }
+
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/builder/Reflections.java b/src/com/github/arteam/simplejsonrpc/client/builder/Reflections.java
new file mode 100644
index 0000000..7de5b46
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/builder/Reflections.java
@@ -0,0 +1,129 @@
+package com.github.arteam.simplejsonrpc.client.builder;
+
+import com.github.arteam.simplejsonrpc.client.JsonRpcId;
+import com.github.arteam.simplejsonrpc.client.JsonRpcParams;
+import com.github.arteam.simplejsonrpc.client.ParamsType;
+import com.github.arteam.simplejsonrpc.client.generator.AtomicLongIdGenerator;
+import com.github.arteam.simplejsonrpc.client.generator.IdGenerator;
+import com.github.arteam.simplejsonrpc.client.metadata.ClassMetadata;
+import com.github.arteam.simplejsonrpc.client.metadata.MethodMetadata;
+import com.github.arteam.simplejsonrpc.client.metadata.ParameterMetadata;
+import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod;
+import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcOptional;
+import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcParam;
+import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Date: 11/17/14
+ * Time: 8:04 PM
+ * Utility class for gathering meta-information about client proxies through reflection
+ *
+ * @author Artem Prigoda
+ */
+class Reflections {
+
+ private Reflections() {
+ }
+
+ /**
+ * Gets remote service interface metadata
+ *
+ * @param clazz an interface for representing a remote service
+ * @return class metadata
+ */
+ @NotNull
+ public static ClassMetadata getClassMetadata(@NotNull Class> clazz) {
+ Map methodsMetadata = new HashMap(32);
+ Class> searchClass = clazz;
+ while (searchClass != null) {
+ JsonRpcService rpcServiceAnn = getAnnotation(searchClass.getAnnotations(), JsonRpcService.class);
+ if (rpcServiceAnn == null) {
+ throw new IllegalStateException("Class '" + clazz.getCanonicalName() +
+ "' is not annotated as @JsonRpcService");
+ }
+ Method[] methods = searchClass.getMethods();
+ for (Method method : methods) {
+ Annotation[] methodAnnotations = method.getDeclaredAnnotations();
+ JsonRpcMethod rpcMethodAnn = getAnnotation(methodAnnotations, JsonRpcMethod.class);
+ if (rpcMethodAnn == null) {
+ throw new IllegalStateException("Method '" + method.getName() + "' is not annotated as @JsonRpcMethod");
+ }
+
+ // LinkedHashMap is needed to support method parameter ordering
+ Map paramsMetadata = new LinkedHashMap(8);
+ Annotation[][] parametersAnnotations = method.getParameterAnnotations();
+ for (int i = 0; i < parametersAnnotations.length; i++) {
+ Annotation[] parametersAnnotation = parametersAnnotations[i];
+ // Check that it's a JSON-RPC param
+ JsonRpcParam rpcParamAnn = getAnnotation(parametersAnnotation, JsonRpcParam.class);
+ if (rpcParamAnn == null) {
+ throw new IllegalStateException("Parameter with index=" + i + " of method '" + method.getName() +
+ "' is not annotated with @JsonRpcParam");
+ }
+ // Check that's a param could be an optional
+ JsonRpcOptional optionalAnn = getAnnotation(parametersAnnotation, JsonRpcOptional.class);
+ ParameterMetadata parameterMetadata = new ParameterMetadata(i, optionalAnn != null);
+ if (paramsMetadata.put(rpcParamAnn.value(), parameterMetadata) != null) {
+ throw new IllegalStateException("Two parameters of method '" + method.getName() + "' have the " +
+ "same name '" + rpcParamAnn.value() + "'");
+ }
+
+ }
+ String name = !rpcMethodAnn.value().isEmpty() ? rpcMethodAnn.value() : method.getName();
+ ParamsType paramsType = getParamsType(methodAnnotations);
+ methodsMetadata.put(method, new MethodMetadata(name, paramsType, paramsMetadata));
+ }
+ searchClass = searchClass.getSuperclass();
+ }
+
+ Annotation[] classAnnotations = clazz.getDeclaredAnnotations();
+ IdGenerator> idGenerator = getIdGenerator(classAnnotations);
+ ParamsType paramsType = getParamsType(classAnnotations);
+ return new ClassMetadata(paramsType, idGenerator, methodsMetadata);
+ }
+
+
+ /**
+ * Get an actual id generator
+ */
+ @NotNull
+ private static IdGenerator> getIdGenerator(@NotNull Annotation[] classAnnotations) {
+ JsonRpcId jsonRpcIdAnn = getAnnotation(classAnnotations, JsonRpcId.class);
+ Class extends IdGenerator>> idGeneratorClazz = (jsonRpcIdAnn == null) ?
+ AtomicLongIdGenerator.class : jsonRpcIdAnn.value();
+ try {
+ return idGeneratorClazz.newInstance();
+ } catch (Exception e) {
+ throw new IllegalStateException("Unable instantiate id generator: " + idGeneratorClazz, e);
+ }
+ }
+
+ @Nullable
+ private static ParamsType getParamsType(@NotNull Annotation[] annotations) {
+ JsonRpcParams rpcParamsAnn = getAnnotation(annotations, JsonRpcParams.class);
+ return rpcParamsAnn != null ? rpcParamsAnn.value() : null;
+
+ }
+
+ @SuppressWarnings("unchecked")
+ @Nullable
+ private static T getAnnotation(@Nullable Annotation[] annotations,
+ @NotNull Class clazz) {
+ if (annotations != null) {
+ for (Annotation annotation : annotations) {
+ if (annotation.annotationType().equals(clazz)) {
+ return (T) annotation;
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/builder/RequestBuilder.java b/src/com/github/arteam/simplejsonrpc/client/builder/RequestBuilder.java
new file mode 100644
index 0000000..c151dab
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/builder/RequestBuilder.java
@@ -0,0 +1,381 @@
+package com.github.arteam.simplejsonrpc.client.builder;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.*;
+import com.fasterxml.jackson.databind.type.SimpleType;
+import com.github.arteam.simplejsonrpc.client.Transport;
+import com.github.arteam.simplejsonrpc.client.exception.JsonRpcException;
+import com.github.arteam.simplejsonrpc.core.domain.ErrorMessage;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Date: 8/9/14
+ * Time: 9:04 PM
+ *
+ * Type-safe builder of JSON-RPC requests.
+ *
+ * It introduces fluent API to build a request, set an expected response type and perform the request.
+ * Builder is immutable: every mutation creates a new object, so it's safe to use in multi-threaded environment.
+ *
+ * It delegates JSON processing to Jackson {@link ObjectMapper} and actual request performing to {@link com.github.arteam.simplejsonrpc.client.Transport}.
+ *
+ * @author Artem Prigoda
+ */
+public class RequestBuilder extends AbstractBuilder {
+
+ /**
+ * JSON-RPC request method
+ */
+ @NotNull
+ private final String method;
+
+ /**
+ * JSON-RPC request id
+ */
+ @NotNull
+ private final ValueNode id;
+
+ /**
+ * JSON-RPC request params as a map
+ */
+ @NotNull
+ private final ObjectNode objectParams;
+
+ /**
+ * JSON-RPC request params as an array
+ */
+ @NotNull
+ private final ArrayNode arrayParams;
+
+ /**
+ * Generic type for representing expected response type
+ */
+ @NotNull
+ private final JavaType javaType;
+
+ /**
+ * Creates a new default request builder without actual parameters
+ *
+ * @param transport transport for request performing
+ * @param mapper mapper for JSON processing
+ */
+ public RequestBuilder(@NotNull Transport transport, @NotNull ObjectMapper mapper) {
+ super(transport, mapper);
+ id = NullNode.instance;
+ objectParams = mapper.createObjectNode();
+ arrayParams = mapper.createArrayNode();
+ method = "";
+ javaType = SimpleType.construct(Object.class);
+ }
+
+ /**
+ * Creates new builder as part of a chain of builders to a full-initialized type-safe builder
+ *
+ * @param transport new transport
+ * @param mapper new mapper
+ * @param method new method
+ * @param id new id
+ * @param objectParams new object params
+ * @param arrayParams new array params
+ * @param javaType new response type
+ */
+ private RequestBuilder(@NotNull Transport transport, @NotNull ObjectMapper mapper, @NotNull String method,
+ @NotNull ValueNode id, @NotNull ObjectNode objectParams, @NotNull ArrayNode arrayParams,
+ @NotNull JavaType javaType) {
+ super(transport, mapper);
+ this.method = method;
+ this.id = id;
+ this.objectParams = objectParams;
+ this.arrayParams = arrayParams;
+ this.javaType = javaType;
+ }
+
+ /**
+ * Sets a request id as a long value
+ *
+ * @param id a request id
+ * @return new builder
+ */
+ @NotNull
+ public RequestBuilder id(@NotNull Long id) {
+ return new RequestBuilder(transport, mapper, method, new LongNode(id), objectParams, arrayParams, javaType);
+ }
+
+ /**
+ * Sets a request id as an integer value
+ *
+ * @param id a request id
+ * @return new builder
+ */
+ @NotNull
+ public RequestBuilder id(@NotNull Integer id) {
+ return new RequestBuilder(transport, mapper, method, new IntNode(id), objectParams, arrayParams, javaType);
+ }
+
+ /**
+ * Sets a request id as a string value
+ *
+ * @param id a request id
+ * @return new builder
+ */
+ @NotNull
+ public RequestBuilder id(@NotNull String id) {
+ return new RequestBuilder(transport, mapper, method, new TextNode(id), objectParams, arrayParams, javaType);
+ }
+
+ /**
+ * Sets a request method
+ *
+ * @param method a request method
+ * @return new builder
+ */
+ @NotNull
+ public RequestBuilder method(@NotNull String method) {
+ return new RequestBuilder(transport, mapper, method, id, objectParams, arrayParams, javaType);
+ }
+
+ /**
+ * Adds a new parameter to current request parameters.
+ *
+ * Caution: If you set request parameters this way, you should follow this convention
+ * during all the building process like that:
+ *
+ * client.createRequest()
+ * .method("find")
+ * .id(43121)
+ * .param("firstName", "Steven")
+ * .param("lastName", "Stamkos")
+ * .returnAs(Player.class)
+ * .execute();
+ *
+ *
+ * Calls to params method are not permitted after this method has been invoked .
+ *
+ * @param name parameter name
+ * @param value parameter value
+ * @return new builder
+ */
+ @NotNull
+ public RequestBuilder param(@NotNull String name, @NotNull Object value) {
+ ObjectNode newObjectParams = objectParams.deepCopy();
+ newObjectParams.set(name, mapper.valueToTree(value));
+ return new RequestBuilder(transport, mapper, method, id, newObjectParams, arrayParams, javaType);
+ }
+
+ /**
+ * Sets request parameters to request parameters.
+ * Parameters are interpreted according to its positions.
+ *
+ * @param values array of parameters
+ * @return new builder
+ */
+ @NotNull
+ public RequestBuilder params(@NotNull Object... values) {
+ return new RequestBuilder(transport, mapper, method, id, objectParams, arrayParams(values), javaType);
+ }
+
+ /**
+ * Sets expected return type. This method is suitable for non-generic types
+ *
+ * @param responseType expected return type
+ * @param new return type
+ * @return new builder
+ */
+ @NotNull
+ public RequestBuilder returnAs(@NotNull Class responseType) {
+ return new RequestBuilder(transport, mapper, method, id, objectParams, arrayParams,
+ SimpleType.construct(responseType));
+ }
+
+ /**
+ * Sets expected return type as a list of objects
+ *
+ * @param elementType type of elements of a list
+ * @param generic list type
+ * @return new builder
+ */
+ @NotNull
+ public RequestBuilder> returnAsList(@NotNull Class elementType) {
+ return new RequestBuilder>(transport, mapper, method, id, objectParams, arrayParams,
+ mapper.getTypeFactory().constructCollectionType(List.class, elementType));
+ }
+
+ /**
+ * Sets expected return type as a set of objects
+ *
+ * @param elementType type of elements of a set
+ * @param generic set type
+ * @return new builder
+ */
+ @NotNull
+ public RequestBuilder> returnAsSet(@NotNull Class elementType) {
+ return new RequestBuilder>(transport, mapper, method, id, objectParams, arrayParams,
+ mapper.getTypeFactory().constructCollectionType(Set.class, elementType));
+ }
+
+ /**
+ * Sets expected return type as a collection of objects.
+ * This method is suitable for non-standard collections like {@link java.util.Queue}
+ *
+ * @param elementType type of elements of a collection
+ * @param generic collection type
+ * @return new builder
+ */
+ @NotNull
+ public RequestBuilder> returnAsCollection(@NotNull Class extends Collection> collectionType,
+ @NotNull Class elementType) {
+ return new RequestBuilder>(transport, mapper, method, id, objectParams, arrayParams,
+ mapper.getTypeFactory().constructCollectionType(collectionType, elementType));
+ }
+
+ /**
+ * Sets expected return type as an array
+ *
+ * @param elementType type of elements of an array
+ * @param generic array type
+ * @return new builder
+ */
+ @NotNull
+ public RequestBuilder returnAsArray(@NotNull Class elementType) {
+ return new RequestBuilder(transport, mapper, method, id, objectParams, arrayParams,
+ mapper.getTypeFactory().constructArrayType(elementType));
+ }
+
+ /**
+ * Sets expected return type as a map of objects.
+ * Because JSON type system the map should have strings as keys.
+ *
+ * @param mapClass expected map interface or implementation,
+ * e.g. {@link java.util.Map}, {@link java.util.HashMap}.
+ * {@link java.util.LinkedHashMap}, {@link java.util.SortedMap}
+ * @param valueType map value type
+ * @param generic map value type
+ * @return new builder
+ */
+ @NotNull
+ public RequestBuilder> returnAsMap(@NotNull Class extends Map> mapClass,
+ @NotNull Class valueType) {
+ return new RequestBuilder>(transport, mapper, method, id, objectParams, arrayParams,
+ mapper.getTypeFactory().constructMapType(mapClass, String.class, valueType));
+ }
+
+ /**
+ * Sets expected return type as a generic type, e.g. Guava Optional.
+ * Generic type is set as a type reference like that:
+ * new TypeReference>() {}
+ *
+ * @param tr type reference
+ * @param a generic type
+ * @return new builder
+ */
+ @NotNull
+ public RequestBuilder returnAs(@NotNull TypeReference tr) {
+ return new RequestBuilder(transport, mapper, method, id, objectParams, arrayParams,
+ mapper.getTypeFactory().constructType(tr.getType()));
+ }
+
+ /**
+ * Execute a request through {@link Transport} and convert a not null response to an expected type
+ *
+ * @return expected not null response
+ * @throws JsonRpcException in case of JSON-RPC error, returned by the server
+ * @throws IllegalStateException if the response is null
+ */
+ @NotNull
+ public T execute() {
+ T result = executeAndConvert();
+ if (result == null) {
+ throw new IllegalStateException("Response is null. Use 'executeNullable' if this is acceptable");
+ }
+ return result;
+ }
+
+ /**
+ * Execute a request through {@link Transport} and convert a nullable response to an expected type
+ *
+ * @return expected response
+ * @throws JsonRpcException in case of JSON-RPC error, returned by the server
+ */
+ @Nullable
+ public T executeNullable() {
+ return executeAndConvert();
+ }
+
+ @Nullable
+ @SuppressWarnings("unchecked")
+ private T executeAndConvert() {
+ String textResponse = executeRequest();
+
+ try {
+ JsonNode responseNode = mapper.readTree(textResponse);
+ JsonNode result = responseNode.get(RESULT);
+ JsonNode error = responseNode.get(ERROR);
+ JsonNode version = responseNode.get(JSONRPC);
+ JsonNode id = responseNode.get(ID);
+
+ if (version == null) {
+ throw new IllegalStateException("Not a JSON-RPC response: " + responseNode);
+ }
+ if (!version.asText().equals(VERSION_2_0)) {
+ throw new IllegalStateException("Bad protocol version in a response: " + responseNode);
+ }
+ if (id == null) {
+ throw new IllegalStateException("Unspecified id in a response: " + responseNode);
+ }
+
+ if (error == null) {
+ if (result != null) {
+ return mapper.convertValue(result, javaType);
+ } else {
+ throw new IllegalStateException("Neither result or error is set in a response: " + responseNode);
+ }
+ } else {
+ ErrorMessage errorMessage = mapper.treeToValue(error, ErrorMessage.class);
+ throw new JsonRpcException(errorMessage);
+ }
+ } catch (JsonProcessingException e) {
+ throw new IllegalStateException("Unable parse a JSON response: " + textResponse, e);
+ } catch (IOException e) {
+ throw new IllegalStateException("I/O error during a response processing", e);
+ }
+ }
+
+ String executeRequest() {
+ ObjectNode requestNode = request(id, method, params());
+ String textRequest;
+ String textResponse;
+ try {
+ textRequest = mapper.writeValueAsString(requestNode);
+ } catch (JsonProcessingException e) {
+ throw new IllegalArgumentException("Unable convert " + requestNode + " to JSON", e);
+ }
+ try {
+ textResponse = transport.pass(textRequest);
+ } catch (IOException e) {
+ throw new IllegalStateException("I/O error during a request processing", e);
+ }
+ return textResponse;
+ }
+
+ @NotNull
+ private JsonNode params() {
+ if (objectParams.size() > 0) {
+ if (arrayParams.size() > 0) {
+ throw new IllegalArgumentException("Both object and array params are set");
+ }
+ return objectParams;
+ }
+ return arrayParams;
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/exception/JsonRpcBatchException.java b/src/com/github/arteam/simplejsonrpc/client/exception/JsonRpcBatchException.java
new file mode 100644
index 0000000..737d723
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/exception/JsonRpcBatchException.java
@@ -0,0 +1,44 @@
+package com.github.arteam.simplejsonrpc.client.exception;
+
+import com.github.arteam.simplejsonrpc.core.domain.ErrorMessage;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+
+/**
+ * Date: 10/13/14
+ * Time: 8:17 PM
+ * Exception that occurs when batch JSON-RPC request is not completely successful
+ *
+ * @author Artem Prigoda
+ */
+public class JsonRpcBatchException extends RuntimeException {
+
+ /**
+ * Succeeded requests
+ */
+ @NotNull
+ private Map, ?> successes;
+
+ /**
+ * Failed requests
+ */
+ @NotNull
+ private Map, ErrorMessage> errors;
+
+ public JsonRpcBatchException(String message, @NotNull Map, ?> successes, @NotNull Map, ErrorMessage> errors) {
+ super(message);
+ this.successes = successes;
+ this.errors = errors;
+ }
+
+ @NotNull
+ public Map, ?> getSuccesses() {
+ return successes;
+ }
+
+ @NotNull
+ public Map, ErrorMessage> getErrors() {
+ return errors;
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/exception/JsonRpcException.java b/src/com/github/arteam/simplejsonrpc/client/exception/JsonRpcException.java
new file mode 100644
index 0000000..4fd740f
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/exception/JsonRpcException.java
@@ -0,0 +1,30 @@
+package com.github.arteam.simplejsonrpc.client.exception;
+
+import com.github.arteam.simplejsonrpc.core.domain.ErrorMessage;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Date: 8/9/14
+ * Time: 10:08 PM
+ * Represents JSON-RPC error returned by a server
+ *
+ * @author Artem Prigoda
+ */
+public class JsonRpcException extends RuntimeException {
+
+ /**
+ * Actual error message
+ */
+ @NotNull
+ private ErrorMessage errorMessage;
+
+ public JsonRpcException(@NotNull ErrorMessage errorMessage) {
+ super(errorMessage.toString());
+ this.errorMessage = errorMessage;
+ }
+
+ @NotNull
+ public ErrorMessage getErrorMessage() {
+ return errorMessage;
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/generator/AtomicLongIdGenerator.java b/src/com/github/arteam/simplejsonrpc/client/generator/AtomicLongIdGenerator.java
new file mode 100644
index 0000000..57ae703
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/generator/AtomicLongIdGenerator.java
@@ -0,0 +1,22 @@
+package com.github.arteam.simplejsonrpc.client.generator;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Date: 12/30/14
+ * Time: 11:19 PM
+ *
+ * Return id from an atomic long counter
+ * It's the most reliable and straightforward way to generate identifiers
+ *
+ * @author Artem Prigoda
+ */
+public class AtomicLongIdGenerator implements IdGenerator {
+
+ private final AtomicLong counter = new AtomicLong(0L);
+
+ @Override
+ public Long generate() {
+ return counter.incrementAndGet();
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/generator/CurrentTimeIdGenerator.java b/src/com/github/arteam/simplejsonrpc/client/generator/CurrentTimeIdGenerator.java
new file mode 100644
index 0000000..13500db
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/generator/CurrentTimeIdGenerator.java
@@ -0,0 +1,16 @@
+package com.github.arteam.simplejsonrpc.client.generator;
+
+/**
+ * Date: 24.08.14
+ * Time: 18:20
+ * Return current time as id.
+ * Not reliable if you need to guarantee uniqueness of request ids
+ *
+ * @author Artem Prigoda
+ */
+public class CurrentTimeIdGenerator implements IdGenerator {
+ @Override
+ public Long generate() {
+ return System.currentTimeMillis();
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/generator/IdGenerator.java b/src/com/github/arteam/simplejsonrpc/client/generator/IdGenerator.java
new file mode 100644
index 0000000..400a6b8
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/generator/IdGenerator.java
@@ -0,0 +1,13 @@
+package com.github.arteam.simplejsonrpc.client.generator;
+
+/**
+ * Date: 24.08.14
+ * Time: 18:12
+ * Strategy for generation request identificators
+ *
+ * @author Artem Prigoda
+ */
+public interface IdGenerator {
+
+ T generate();
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/generator/SecureRandomIdGenerator.java b/src/com/github/arteam/simplejsonrpc/client/generator/SecureRandomIdGenerator.java
new file mode 100644
index 0000000..41d12f7
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/generator/SecureRandomIdGenerator.java
@@ -0,0 +1,36 @@
+package com.github.arteam.simplejsonrpc.client.generator;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+/**
+ * Date: 1/12/15
+ * Time: 11:17 PM
+ * Abstract generator of secure random identifiers
+ *
+ * @author Artem Prigoda
+ */
+abstract class SecureRandomIdGenerator implements IdGenerator {
+
+ private static final String SHA_1_PRNG = "SHA1PRNG";
+
+ @NotNull
+ protected final SecureRandom secureRandom;
+
+ protected SecureRandomIdGenerator() {
+ secureRandom = initSecureRandom();
+ }
+
+ @NotNull
+ private static SecureRandom initSecureRandom() {
+ try {
+ SecureRandom secureRandom = SecureRandom.getInstance(SHA_1_PRNG);
+ secureRandom.nextBytes(new byte[]{}); // Important to seed immediately after creation
+ return secureRandom;
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/generator/SecureRandomIntIdGenerator.java b/src/com/github/arteam/simplejsonrpc/client/generator/SecureRandomIntIdGenerator.java
new file mode 100644
index 0000000..de5899e
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/generator/SecureRandomIntIdGenerator.java
@@ -0,0 +1,32 @@
+package com.github.arteam.simplejsonrpc.client.generator;
+
+/**
+ * Date: 1/12/15
+ * Time: 11:38 PM
+ * Generates secure random positive integers under limit
+ * By default limit is 65536
+ *
+ * @author Artem Prigoda
+ */
+public class SecureRandomIntIdGenerator extends SecureRandomIdGenerator {
+
+ private static final int DEFAULT_LIMIT = 65536;
+
+ private final int limit;
+
+ public SecureRandomIntIdGenerator() {
+ limit = DEFAULT_LIMIT;
+ }
+
+ public SecureRandomIntIdGenerator(int limit) {
+ if (limit <= 0) {
+ throw new IllegalArgumentException("Limit should be positive");
+ }
+ this.limit = limit;
+ }
+
+ @Override
+ public Integer generate() {
+ return secureRandom.nextInt(limit);
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/generator/SecureRandomLongIdGenerator.java b/src/com/github/arteam/simplejsonrpc/client/generator/SecureRandomLongIdGenerator.java
new file mode 100644
index 0000000..5856042
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/generator/SecureRandomLongIdGenerator.java
@@ -0,0 +1,16 @@
+package com.github.arteam.simplejsonrpc.client.generator;
+
+/**
+ * Date: 1/12/15
+ * Time: 11:12 PM
+ * Generate secure random positive long identifiers
+ *
+ * @author Artem Prigoda
+ */
+public class SecureRandomLongIdGenerator extends SecureRandomIdGenerator {
+
+ @Override
+ public Long generate() {
+ return secureRandom.nextLong() >>> 1;
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/generator/SecureRandomStringIdGenerator.java b/src/com/github/arteam/simplejsonrpc/client/generator/SecureRandomStringIdGenerator.java
new file mode 100644
index 0000000..71269ae
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/generator/SecureRandomStringIdGenerator.java
@@ -0,0 +1,65 @@
+package com.github.arteam.simplejsonrpc.client.generator;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Date: 12/30/14
+ * Time: 11:45 PM
+ * Generate secure random strings consisting of HEX symbols with length of 40 characters
+ *
+ * @author Artem Prigoda
+ */
+public class SecureRandomStringIdGenerator extends SecureRandomIdGenerator {
+
+ private static final char[] ALPHABET = "0123456789abcdef".toCharArray();
+ private static final int DEFAULT_CHUNK_SIZE = 20;
+
+ private final int chunkSize;
+
+ /**
+ * Create a default generator
+ */
+ public SecureRandomStringIdGenerator() {
+ chunkSize = DEFAULT_CHUNK_SIZE;
+ }
+
+ /**
+ * Create generator with a specific identifiers length
+ *
+ * @param idLength custom identifier length (it should pow of 2)
+ */
+ public SecureRandomStringIdGenerator(int idLength) {
+ if (idLength < 2) {
+ throw new IllegalArgumentException("Bad message length: '" + idLength + "'. It should be >= 2");
+ }
+ this.chunkSize = idLength / 2;
+ }
+
+ @Override
+ public String generate() {
+ byte[] buffer = new byte[chunkSize];
+ secureRandom.nextBytes(buffer);
+ return hexString(buffer);
+ }
+
+
+ /**
+ * Convert binary data to HEX representation.
+ * Every byte is converted to 2 HEX symbols (one symbol for every 4 bits)
+ *
+ * @param source source chunk of data
+ * @return string representation of the chunk as HEX values
+ */
+ @NotNull
+ private static String hexString(@NotNull byte[] source) {
+ char[] result = new char[source.length * 2];
+ for (int i = 0; i < source.length; i++) {
+ int unsigned = source[i] & 0xFF;
+ int first4Bytes = unsigned >>> 4;
+ int last4Bytes = unsigned & 0x0F;
+ result[i * 2] = ALPHABET[first4Bytes];
+ result[i * 2 + 1] = ALPHABET[last4Bytes];
+ }
+ return new String(result);
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/metadata/ClassMetadata.java b/src/com/github/arteam/simplejsonrpc/client/metadata/ClassMetadata.java
new file mode 100644
index 0000000..2b0f488
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/metadata/ClassMetadata.java
@@ -0,0 +1,53 @@
+package com.github.arteam.simplejsonrpc.client.metadata;
+
+import com.github.arteam.simplejsonrpc.client.ParamsType;
+import com.github.arteam.simplejsonrpc.client.generator.IdGenerator;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ * Date: 8/1/14
+ * Time: 7:42 PM
+ * Metadata about a Java class
+ *
+ * @author Artem Prigoda
+ */
+public class ClassMetadata {
+
+ @Nullable
+ private final ParamsType paramsType;
+
+ @NotNull
+ private final IdGenerator> idGenerator;
+
+ /**
+ * Map of JSON-RPC 2.0 methods by rpc name
+ */
+ @NotNull
+ private final Map methods;
+
+ public ClassMetadata(@Nullable ParamsType paramsType, @NotNull IdGenerator> idGenerator,
+ @NotNull Map methods) {
+ this.paramsType = paramsType;
+ this.idGenerator = idGenerator;
+ this.methods = methods;
+ }
+
+ @Nullable
+ public ParamsType getParamsType() {
+ return paramsType;
+ }
+
+ @NotNull
+ public IdGenerator> getIdGenerator() {
+ return idGenerator;
+ }
+
+ @NotNull
+ public Map getMethods() {
+ return methods;
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/metadata/MethodMetadata.java b/src/com/github/arteam/simplejsonrpc/client/metadata/MethodMetadata.java
new file mode 100644
index 0000000..62e0cdf
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/metadata/MethodMetadata.java
@@ -0,0 +1,57 @@
+package com.github.arteam.simplejsonrpc.client.metadata;
+
+import com.github.arteam.simplejsonrpc.client.ParamsType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ * Date: 8/1/14
+ * Time: 7:42 PM
+ * Metadata about a Java method
+ *
+ * @author Artem Prigoda
+ */
+public class MethodMetadata {
+
+ @NotNull
+ private final String name;
+
+ @Nullable
+ private final ParamsType paramsType;
+
+ /**
+ * Map of method params by RPC name
+ */
+ @NotNull
+ private final Map params;
+
+ public MethodMetadata(@NotNull String name, @Nullable ParamsType paramsType, @NotNull Map params) {
+ this.params = params;
+ this.name = name;
+ this.paramsType = paramsType;
+ }
+ @NotNull
+ public Map getParams() {
+ return params;
+ }
+
+ @NotNull
+ public String getName() {
+ return name;
+ }
+
+ @Nullable
+ public ParamsType getParamsType() {
+ return paramsType;
+ }
+
+ @Override
+ public String toString() {
+ return "MethodMetadata{" +
+ " params=" + params +
+ '}';
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/client/metadata/ParameterMetadata.java b/src/com/github/arteam/simplejsonrpc/client/metadata/ParameterMetadata.java
new file mode 100644
index 0000000..e89fb28
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/client/metadata/ParameterMetadata.java
@@ -0,0 +1,42 @@
+package com.github.arteam.simplejsonrpc.client.metadata;
+
+/**
+ * Date: 8/1/14
+ * Time: 7:44 PM
+ * Method parameter metadata
+ *
+ * @author Artem Prigoda
+ */
+public class ParameterMetadata {
+
+ /**
+ * Index in method arguments
+ */
+ private final int index;
+
+ /**
+ * Whether parameter is optional
+ */
+ private final boolean optional;
+
+ public ParameterMetadata(int index, boolean optional) {
+ this.index = index;
+ this.optional = optional;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public boolean isOptional() {
+ return optional;
+ }
+
+ @Override
+ public String toString() {
+ return "ParameterMetadata{" +
+ "index=" + index +
+ ", optional=" + optional +
+ '}';
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcError.java b/src/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcError.java
new file mode 100644
index 0000000..089bbd5
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcError.java
@@ -0,0 +1,33 @@
+package com.github.arteam.simplejsonrpc.core.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Date: 7/31/14
+ * Time: 6:03 PM
+ * Annotation for marking an exception as a JSON-RPC error
+ *
+ * @author Artem Prigoda
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JsonRpcError {
+
+ /**
+ * JSON-RPC error code
+ *
+ * @return error code
+ */
+ int code() default 0;
+
+ /**
+ * JSON-RPC error message.
+ * If empty then the exception message will be used
+ *
+ * @return error message
+ */
+ String message() default "";
+}
diff --git a/src/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcMethod.java b/src/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcMethod.java
new file mode 100644
index 0000000..c577792
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcMethod.java
@@ -0,0 +1,27 @@
+package com.github.arteam.simplejsonrpc.core.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Date: 07.06.14
+ * Time: 13:02
+ *
+ * Annotation for marking a method as eligible for calling from the web.
+ * Makes sense only for public non-static methods.
+ *
+ * @author Artem Prigoda
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JsonRpcMethod {
+
+ /**
+ * Method RPC name. By default the actual method name is used.
+ *
+ * @return method RPC name
+ */
+ String value() default "";
+}
diff --git a/src/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcOptional.java b/src/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcOptional.java
new file mode 100644
index 0000000..bb61e36
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcOptional.java
@@ -0,0 +1,22 @@
+package com.github.arteam.simplejsonrpc.core.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Date: 6/15/14
+ * Time: 1:49 AM
+ *
+ * Annotation for marking a parameter as an optional.
+ *
+ * It means a client isn't forced to pass this parameter to the method. If the client doesn't provide it,
+ * {@code null} value is used for complex types and an appropriate default value for primitives.
+ *
+ * @author Artem Prigoda
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JsonRpcOptional {
+}
diff --git a/src/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcParam.java b/src/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcParam.java
new file mode 100644
index 0000000..7e83af8
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcParam.java
@@ -0,0 +1,32 @@
+package com.github.arteam.simplejsonrpc.core.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Date: 07.06.14
+ * Time: 13:02
+ * Annotation for marking RPC method parameter.
+ *
+ * Because Java doesn't retain information about method names in a class file and
+ * therefore this information is not available in runtime, this annotation MUST
+ * be placed on all the method parameters.
+ *
+ * Otherwise {@link IllegalArgumentException} will be generated in runtime and
+ * an error message will be returned to a client.
+ *
+ * @author Artem Prigoda
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JsonRpcParam {
+
+ /**
+ * RPC method parameter name. MUST be specified.
+ *
+ * @return parameter name
+ */
+ public String value();
+}
diff --git a/src/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcService.java b/src/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcService.java
new file mode 100644
index 0000000..c8f5f87
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcService.java
@@ -0,0 +1,16 @@
+package com.github.arteam.simplejsonrpc.core.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * Date: 8/2/14
+ * Time: 6:10 PM
+ * Annotation for marking a service as a JSON-RPC service
+ *
+ * @author Artem Prigoda
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface JsonRpcService {
+}
diff --git a/src/com/github/arteam/simplejsonrpc/core/domain/ErrorMessage.java b/src/com/github/arteam/simplejsonrpc/core/domain/ErrorMessage.java
new file mode 100644
index 0000000..5d0ef1c
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/core/domain/ErrorMessage.java
@@ -0,0 +1,42 @@
+package com.github.arteam.simplejsonrpc.core.domain;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Date: 07.06.14
+ * Time: 15:16
+ *
+ * Representation of a JSON-RPC error message
+ *
+ * @author Artem Prigoda
+ */
+public class ErrorMessage {
+
+ @JsonProperty("code")
+ private final int code;
+
+ @NotNull
+ @JsonProperty("message")
+ private final String message;
+
+ public ErrorMessage(@JsonProperty("code") int code,
+ @JsonProperty("message") @NotNull String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ @NotNull
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public String toString() {
+ return "ErrorMessage{code=" + code + ", message=" + message + "}";
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/core/domain/ErrorResponse.java b/src/com/github/arteam/simplejsonrpc/core/domain/ErrorResponse.java
new file mode 100644
index 0000000..5deb652
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/core/domain/ErrorResponse.java
@@ -0,0 +1,39 @@
+package com.github.arteam.simplejsonrpc.core.domain;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.node.NullNode;
+import com.fasterxml.jackson.databind.node.ValueNode;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Date: 07.06.14
+ * Time: 12:35
+ *
+ * Representation of a JSON-RPC error response
+ *
+ * @author Artem Prigoda
+ */
+public class ErrorResponse extends Response {
+
+ @NotNull
+ @JsonProperty("error")
+ private final ErrorMessage error;
+
+ @JsonCreator
+ public ErrorResponse(@JsonProperty("id") @NotNull ValueNode id,
+ @JsonProperty("error") @NotNull ErrorMessage error) {
+ super(id);
+ this.error = error;
+ }
+
+ public ErrorResponse(@NotNull ErrorMessage error) {
+ super(NullNode.getInstance());
+ this.error = error;
+ }
+
+ @NotNull
+ public ErrorMessage getError() {
+ return error;
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/core/domain/Request.java b/src/com/github/arteam/simplejsonrpc/core/domain/Request.java
new file mode 100644
index 0000000..9888a8b
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/core/domain/Request.java
@@ -0,0 +1,66 @@
+package com.github.arteam.simplejsonrpc.core.domain;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ValueNode;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+
+/**
+ * Date: 07.06.14
+ * Time: 12:24
+ *
+ * Representation of a JSON-RPC request
+ *
+ * @author Artem Prigoda
+ */
+public class Request {
+
+ @Nullable
+ private final String jsonrpc;
+
+ @Nullable
+ private final String method;
+
+ @NotNull
+ private final JsonNode params;
+
+ @NotNull
+ private final ValueNode id;
+
+ public Request(@JsonProperty("jsonrpc") @Nullable String jsonrpc,
+ @JsonProperty("method") @Nullable String method,
+ @JsonProperty("params") @NotNull JsonNode params,
+ @JsonProperty("id") @NotNull ValueNode id) {
+ this.jsonrpc = jsonrpc;
+ this.method = method;
+ this.id = id;
+ this.params = params;
+ }
+
+ @Nullable
+ public String getJsonrpc() {
+ return jsonrpc;
+ }
+
+ @Nullable
+ public String getMethod() {
+ return method;
+ }
+
+ @NotNull
+ public ValueNode getId() {
+ return id;
+ }
+
+ @NotNull
+ public JsonNode getParams() {
+ return params;
+ }
+
+ @Override
+ public String toString() {
+ return "Request{jsonrpc=" + jsonrpc + ", method=" + method + ", id=" + id + ", params=" + params + "}";
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/core/domain/Response.java b/src/com/github/arteam/simplejsonrpc/core/domain/Response.java
new file mode 100644
index 0000000..8dacc41
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/core/domain/Response.java
@@ -0,0 +1,46 @@
+package com.github.arteam.simplejsonrpc.core.domain;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.node.ValueNode;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Date: 07.06.14
+ * Time: 12:34
+ *
+ * Base representation of a JSON-RPC response (success or error)
+ *
+ * @author Artem Prigoda
+ */
+public class Response {
+
+ private static final String VERSION = "2.0";
+
+ @NotNull
+ @JsonProperty("jsonrpc")
+ private final String jsonrpc;
+
+ @NotNull
+ @JsonProperty("id")
+ private final ValueNode id;
+
+ public Response(@NotNull ValueNode id) {
+ this.id = id;
+ jsonrpc = VERSION;
+ }
+
+ public Response(@NotNull ValueNode id, @NotNull String jsonrpc) {
+ this.id = id;
+ this.jsonrpc = jsonrpc;
+ }
+
+ @NotNull
+ public String getJsonrpc() {
+ return jsonrpc;
+ }
+
+ @NotNull
+ public ValueNode getId() {
+ return id;
+ }
+}
diff --git a/src/com/github/arteam/simplejsonrpc/core/domain/SuccessResponse.java b/src/com/github/arteam/simplejsonrpc/core/domain/SuccessResponse.java
new file mode 100644
index 0000000..6023529
--- /dev/null
+++ b/src/com/github/arteam/simplejsonrpc/core/domain/SuccessResponse.java
@@ -0,0 +1,31 @@
+package com.github.arteam.simplejsonrpc.core.domain;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.node.ValueNode;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Date: 07.06.14
+ * Time: 12:31
+ * Representation of a successful JSON-RPC response
+ *
+ * @author Artem Prigoda
+ */
+public class SuccessResponse extends Response {
+
+ @Nullable
+ @JsonProperty("result")
+ private final Object result;
+
+ public SuccessResponse(@JsonProperty("id") @NotNull ValueNode id,
+ @JsonProperty("result") @Nullable Object result) {
+ super(id);
+ this.result = result;
+ }
+
+ @Nullable
+ public Object getResult() {
+ return result;
+ }
+}
diff --git a/src/team492/AdbBridge.java b/src/team492/AdbBridge.java
new file mode 100644
index 0000000..c0a2300
--- /dev/null
+++ b/src/team492/AdbBridge.java
@@ -0,0 +1,98 @@
+package team492;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * AdbBridge interfaces to an Android Debug Bridge (adb) binary, which is needed
+ * to communicate to Android devices over USB.
+ *
+ * adb binary provided by https://github.com/Spectrum3847/RIOdroid
+ */
+public class AdbBridge
+{
+ Path bin_location_;
+ public final static Path DEFAULT_LOCATION = Paths.get("/usr/bin/adb");
+
+ public AdbBridge()
+ {
+ Path adb_location;
+ String env_val = System.getenv("FRC_ADB_LOCATION");
+ if (env_val == null || "".equals(env_val))
+ {
+ adb_location = DEFAULT_LOCATION;
+ }
+ else
+ {
+ adb_location = Paths.get(env_val);
+ }
+ bin_location_ = adb_location;
+ }
+
+ public AdbBridge(Path location)
+ {
+ bin_location_ = location;
+ }
+
+ private boolean runCommand(String args)
+ {
+ Runtime r = Runtime.getRuntime();
+ String cmd = bin_location_.toString() + " " + args;
+
+ try
+ {
+ Process p = r.exec(cmd);
+ p.waitFor();
+ }
+ catch (IOException e)
+ {
+ System.err.println("AdbBridge: Could not run command " + cmd);
+ e.printStackTrace();
+ return false;
+ }
+ catch (InterruptedException e)
+ {
+ System.err.println("AdbBridge: Could not run command " + cmd);
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+
+ public void start()
+ {
+ System.out.println("Starting adb");
+ runCommand("start");
+ }
+
+ public void stop()
+ {
+ System.out.println("Stopping adb");
+ runCommand("kill-server");
+ }
+
+ public void restartAdb()
+ {
+ System.out.println("Restarting adb");
+ stop();
+ start();
+ }
+
+ public void portForward(int local_port, int remote_port)
+ {
+ runCommand("forward tcp:" + local_port + " tcp:" + remote_port);
+ }
+
+ public void reversePortForward(int remote_port, int local_port)
+ {
+ runCommand("reverse tcp:" + remote_port + " tcp:" + local_port);
+ }
+
+ public void restartApp()
+ {
+ System.out.println("Restarting app");
+ runCommand("shell am force-stop com.team254.cheezdroid \\; "
+ + "am start com.team254.cheezdroid/com.team254.cheezdroid.VisionTrackerActivity");
+ }
+}
diff --git a/src/team492/AwooCommunicator.java b/src/team492/AwooCommunicator.java
new file mode 100644
index 0000000..83ad1ab
--- /dev/null
+++ b/src/team492/AwooCommunicator.java
@@ -0,0 +1,103 @@
+package team492;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.Socket;
+
+import com.github.arteam.simplejsonrpc.client.Transport;
+
+public class AwooCommunicator implements Transport
+{
+ private int port;
+ private BufferedWriter bw;
+ private Socket kemono;
+
+ public AwooCommunicator(int port)
+ {
+ this.port = port;
+ }
+
+ public void initCommunicator()
+ {
+ try
+ {
+ System.out.println("Attempting communication to port " + port);
+ kemono = new Socket("127.0.0.1", port);
+ bw = new BufferedWriter(new OutputStreamWriter(kemono.getOutputStream()));
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void sendMessage(String msg)
+ {
+ try
+ {
+ if (kemono == null)
+ {
+ return;
+ }
+ if(!kemono.isClosed() && kemono.isConnected())
+ {
+ try
+ {
+ bw.write(msg+"\n");
+ bw.flush();
+ }
+ catch (IOException ioe)
+ {
+ ioe.printStackTrace();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void implodeCommunicator()
+ {
+ try
+ {
+ bw.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ bw = null;
+ try
+ {
+ kemono.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ kemono = null;
+ port = -1;
+ }
+
+ @Override
+ public String pass(String request) throws IOException
+ {
+ sendMessage(request);
+ String toRet = "";
+ InputStream istream = kemono.getInputStream();
+ BufferedReader receiveRead = new BufferedReader(new InputStreamReader(istream));
+ String receiveMessage = "";
+ if((receiveMessage = receiveRead.readLine()) != null)
+ {
+ toRet = (receiveMessage + "\n");
+ }
+ return toRet;
+ }
+}
+
diff --git a/src/team492/Robot.java b/src/team492/Robot.java
index b5885c0..f3ce923 100644
--- a/src/team492/Robot.java
+++ b/src/team492/Robot.java
@@ -23,6 +23,8 @@
package team492;
import com.ctre.phoenix.motorcontrol.FeedbackDevice;
+import com.github.arteam.simplejsonrpc.client.JsonRpcClient;
+
import edu.wpi.cscore.UsbCamera;
import edu.wpi.first.wpilibj.AnalogInput;
import edu.wpi.first.wpilibj.CameraServer;
@@ -195,6 +197,11 @@ public Robot()
super(programName);
} //Robot
+
+ public AdbBridge adbBridge;
+ public AwooCommunicator awooCommunicator;
+ public JsonRpcClient rpcClient;
+
/**
* This function is run when the robot is first started up and should be used for any initialization code.
*/
@@ -383,6 +390,24 @@ else if(USE_PIXY_I2C)
diagnostics = new OnBoardDiagnostics(this);
+
+ // ayyioasjfasfdjisagoibdsaofisajo;bas;ofjsaiojfasiojgisaofj
+ adbBridge = new AdbBridge();
+ adbBridge.start();
+ adbBridge.portForward(13970, 13970);
+
+ awooCommunicator = new AwooCommunicator(13970);
+ awooCommunicator.initCommunicator();
+
+ rpcClient = new JsonRpcClient(awooCommunicator);
+
+ // awooCommunicator.sendMessage("Test message from RoboRIO");
+
+ @SuppressWarnings("unused")
+ TestRPCClass remoteInitializationInstance = rpcClient.createRequest().method("getMajiraInstance").id(1).returnAs(TestRPCClass.class).execute();
+
+ System.out.println(remoteInitializationInstance.toString());
+
//
// Create Robot Modes.
//
@@ -403,12 +428,14 @@ public void robotStartMode(RunMode runMode, RunMode prevMode)
{
// Robot is safe.
// Note: "disaibled" is not a typo. It forces the speech board to pronounce it correctly.
+ // awooCommunicator.sendMessage("Robot disabled");
tts.speak("Robot disaibled");
nextTimeToSpeakInSeconds = TrcUtil.getCurrentTime() + IDLE_PERIOD_SECONDS;
}
else
{
// Robot is unsafe
+ // awooCommunicator.sendMessage("Robot enabled, stand clear.");
tts.speak("Robot enabled, stand clear");
nextTimeToSpeakInSeconds = TrcUtil.getCurrentTime() + SPEAK_PERIOD_SECONDS;
}
@@ -873,3 +900,4 @@ public double constrainForcePercentageByElevatorHeight(double desiredForcePercen
}
} //class Robot
+
diff --git a/src/team492/TestRPCClass.java b/src/team492/TestRPCClass.java
new file mode 100644
index 0000000..cd1e824
--- /dev/null
+++ b/src/team492/TestRPCClass.java
@@ -0,0 +1,45 @@
+package team492;
+
+import java.util.Date;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class TestRPCClass
+{
+ @JsonProperty
+ private String firstName;
+
+ @JsonProperty
+ private String lastName;
+
+ @JsonProperty
+ private long timeInitialized = 0;
+
+
+ @JsonCreator
+ public TestRPCClass()
+ {
+ firstName = "Tail";
+ lastName = "Fuzzball";
+ }
+
+ @JsonCreator
+ public TestRPCClass(@JsonProperty("firstName") String firstName, @JsonProperty("lastName") String lastName)
+ {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public void initTime()
+ {
+ Date date = new Date();
+ timeInitialized = date.getTime();
+ }
+
+ @Override
+ public String toString()
+ {
+ return firstName + " " + lastName + " " + timeInitialized;
+ }
+}
diff --git a/src/team492/TestRPCService.java b/src/team492/TestRPCService.java
new file mode 100644
index 0000000..e6bd6e6
--- /dev/null
+++ b/src/team492/TestRPCService.java
@@ -0,0 +1,12 @@
+package team492;
+
+import com.github.arteam.simplejsonrpc.client.JsonRpcId;
+import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod;
+import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService;
+
+@JsonRpcService
+public interface TestRPCService
+{
+ @JsonRpcMethod("getMajiraInstance")
+ public TestRPCClass getMajiraInstance();
+}