| #include "java_generator.h" |
| |
| #include <algorithm> |
| #include <iostream> |
| #include <iterator> |
| #include <map> |
| #include <vector> |
| #include <google/protobuf/compiler/java/java_names.h> |
| #include <google/protobuf/descriptor.h> |
| #include <google/protobuf/io/printer.h> |
| #include <google/protobuf/io/zero_copy_stream.h> |
| |
| // Stringify helpers used solely to cast GRPC_VERSION |
| #ifndef STR |
| #define STR(s) #s |
| #endif |
| |
| #ifndef XSTR |
| #define XSTR(s) STR(s) |
| #endif |
| |
| #ifndef FALLTHROUGH_INTENDED |
| #define FALLTHROUGH_INTENDED |
| #endif |
| |
| namespace java_grpc_generator { |
| |
| using google::protobuf::FileDescriptor; |
| using google::protobuf::ServiceDescriptor; |
| using google::protobuf::MethodDescriptor; |
| using google::protobuf::Descriptor; |
| using google::protobuf::io::Printer; |
| using google::protobuf::SourceLocation; |
| using std::to_string; |
| |
| // Adjust a method name prefix identifier to follow the JavaBean spec: |
| // - decapitalize the first letter |
| // - remove embedded underscores & capitalize the following letter |
| static string MixedLower(const string& word) { |
| string w; |
| w += tolower(word[0]); |
| bool after_underscore = false; |
| for (size_t i = 1; i < word.length(); ++i) { |
| if (word[i] == '_') { |
| after_underscore = true; |
| } else { |
| w += after_underscore ? toupper(word[i]) : word[i]; |
| after_underscore = false; |
| } |
| } |
| return w; |
| } |
| |
| // Converts to the identifier to the ALL_UPPER_CASE format. |
| // - An underscore is inserted where a lower case letter is followed by an |
| // upper case letter. |
| // - All letters are converted to upper case |
| static string ToAllUpperCase(const string& word) { |
| string w; |
| for (size_t i = 0; i < word.length(); ++i) { |
| w += toupper(word[i]); |
| if ((i < word.length() - 1) && islower(word[i]) && isupper(word[i + 1])) { |
| w += '_'; |
| } |
| } |
| return w; |
| } |
| |
| static inline string LowerMethodName(const MethodDescriptor* method) { |
| return MixedLower(method->name()); |
| } |
| |
| static inline string MethodPropertiesFieldName(const MethodDescriptor* method) { |
| return "METHOD_" + ToAllUpperCase(method->name()); |
| } |
| |
| static inline string MethodPropertiesGetterName(const MethodDescriptor* method) { |
| return MixedLower("get_" + method->name() + "_method"); |
| } |
| |
| static inline string MethodPropertiesGetterHelperName(const MethodDescriptor* method) { |
| return MixedLower("get_" + method->name() + "_method_helper"); |
| } |
| |
| static inline string MethodIdFieldName(const MethodDescriptor* method) { |
| return "METHODID_" + ToAllUpperCase(method->name()); |
| } |
| |
| static inline string MessageFullJavaName(bool nano, const Descriptor* desc) { |
| string name = google::protobuf::compiler::java::ClassName(desc); |
| if (nano) { |
| // XXX: Add "nano" to the original package |
| // (https://github.com/grpc/grpc-java/issues/900) |
| if (isupper(name[0])) { |
| // No java package specified. |
| return "nano." + name; |
| } |
| for (size_t i = 0; i < name.size(); ++i) { |
| if ((name[i] == '.') && (i < (name.size() - 1)) && isupper(name[i + 1])) { |
| return name.substr(0, i + 1) + "nano." + name.substr(i + 1); |
| } |
| } |
| } |
| return name; |
| } |
| |
| // TODO(nmittler): Remove once protobuf includes javadoc methods in distribution. |
| template <typename ITR> |
| static void GrpcSplitStringToIteratorUsing(const string& full, |
| const char* delim, |
| ITR& result) { |
| // Optimize the common case where delim is a single character. |
| if (delim[0] != '\0' && delim[1] == '\0') { |
| char c = delim[0]; |
| const char* p = full.data(); |
| const char* end = p + full.size(); |
| while (p != end) { |
| if (*p == c) { |
| ++p; |
| } else { |
| const char* start = p; |
| while (++p != end && *p != c); |
| *result++ = string(start, p - start); |
| } |
| } |
| return; |
| } |
| |
| string::size_type begin_index, end_index; |
| begin_index = full.find_first_not_of(delim); |
| while (begin_index != string::npos) { |
| end_index = full.find_first_of(delim, begin_index); |
| if (end_index == string::npos) { |
| *result++ = full.substr(begin_index); |
| return; |
| } |
| *result++ = full.substr(begin_index, (end_index - begin_index)); |
| begin_index = full.find_first_not_of(delim, end_index); |
| } |
| } |
| |
| // TODO(nmittler): Remove once protobuf includes javadoc methods in distribution. |
| static void GrpcSplitStringUsing(const string& full, |
| const char* delim, |
| std::vector<string>* result) { |
| std::back_insert_iterator< std::vector<string> > it(*result); |
| GrpcSplitStringToIteratorUsing(full, delim, it); |
| } |
| |
| // TODO(nmittler): Remove once protobuf includes javadoc methods in distribution. |
| static std::vector<string> GrpcSplit(const string& full, const char* delim) { |
| std::vector<string> result; |
| GrpcSplitStringUsing(full, delim, &result); |
| return result; |
| } |
| |
| // TODO(nmittler): Remove once protobuf includes javadoc methods in distribution. |
| static string GrpcEscapeJavadoc(const string& input) { |
| string result; |
| result.reserve(input.size() * 2); |
| |
| char prev = '*'; |
| |
| for (string::size_type i = 0; i < input.size(); i++) { |
| char c = input[i]; |
| switch (c) { |
| case '*': |
| // Avoid "/*". |
| if (prev == '/') { |
| result.append("*"); |
| } else { |
| result.push_back(c); |
| } |
| break; |
| case '/': |
| // Avoid "*/". |
| if (prev == '*') { |
| result.append("/"); |
| } else { |
| result.push_back(c); |
| } |
| break; |
| case '@': |
| // '@' starts javadoc tags including the @deprecated tag, which will |
| // cause a compile-time error if inserted before a declaration that |
| // does not have a corresponding @Deprecated annotation. |
| result.append("@"); |
| break; |
| case '<': |
| // Avoid interpretation as HTML. |
| result.append("<"); |
| break; |
| case '>': |
| // Avoid interpretation as HTML. |
| result.append(">"); |
| break; |
| case '&': |
| // Avoid interpretation as HTML. |
| result.append("&"); |
| break; |
| case '\\': |
| // Java interprets Unicode escape sequences anywhere! |
| result.append("\"); |
| break; |
| default: |
| result.push_back(c); |
| break; |
| } |
| |
| prev = c; |
| } |
| |
| return result; |
| } |
| |
| // TODO(nmittler): Remove once protobuf includes javadoc methods in distribution. |
| template <typename DescriptorType> |
| static string GrpcGetCommentsForDescriptor(const DescriptorType* descriptor) { |
| SourceLocation location; |
| if (descriptor->GetSourceLocation(&location)) { |
| return location.leading_comments.empty() ? |
| location.trailing_comments : location.leading_comments; |
| } |
| return string(); |
| } |
| |
| // TODO(nmittler): Remove once protobuf includes javadoc methods in distribution. |
| static std::vector<string> GrpcGetDocLines(const string& comments) { |
| if (!comments.empty()) { |
| // TODO(kenton): Ideally we should parse the comment text as Markdown and |
| // write it back as HTML, but this requires a Markdown parser. For now |
| // we just use <pre> to get fixed-width text formatting. |
| |
| // If the comment itself contains block comment start or end markers, |
| // HTML-escape them so that they don't accidentally close the doc comment. |
| string escapedComments = GrpcEscapeJavadoc(comments); |
| |
| std::vector<string> lines = GrpcSplit(escapedComments, "\n"); |
| while (!lines.empty() && lines.back().empty()) { |
| lines.pop_back(); |
| } |
| return lines; |
| } |
| return std::vector<string>(); |
| } |
| |
| // TODO(nmittler): Remove once protobuf includes javadoc methods in distribution. |
| template <typename DescriptorType> |
| static std::vector<string> GrpcGetDocLinesForDescriptor(const DescriptorType* descriptor) { |
| return GrpcGetDocLines(GrpcGetCommentsForDescriptor(descriptor)); |
| } |
| |
| // TODO(nmittler): Remove once protobuf includes javadoc methods in distribution. |
| static void GrpcWriteDocCommentBody(Printer* printer, |
| const std::vector<string>& lines, |
| bool surroundWithPreTag) { |
| if (!lines.empty()) { |
| if (surroundWithPreTag) { |
| printer->Print(" * <pre>\n"); |
| } |
| |
| for (size_t i = 0; i < lines.size(); i++) { |
| // Most lines should start with a space. Watch out for lines that start |
| // with a /, since putting that right after the leading asterisk will |
| // close the comment. |
| if (!lines[i].empty() && lines[i][0] == '/') { |
| printer->Print(" * $line$\n", "line", lines[i]); |
| } else { |
| printer->Print(" *$line$\n", "line", lines[i]); |
| } |
| } |
| |
| if (surroundWithPreTag) { |
| printer->Print(" * </pre>\n"); |
| } |
| } |
| } |
| |
| // TODO(nmittler): Remove once protobuf includes javadoc methods in distribution. |
| static void GrpcWriteDocComment(Printer* printer, const string& comments) { |
| printer->Print("/**\n"); |
| std::vector<string> lines = GrpcGetDocLines(comments); |
| GrpcWriteDocCommentBody(printer, lines, false); |
| printer->Print(" */\n"); |
| } |
| |
| // TODO(nmittler): Remove once protobuf includes javadoc methods in distribution. |
| static void GrpcWriteServiceDocComment(Printer* printer, |
| const ServiceDescriptor* service) { |
| // Deviating from protobuf to avoid extraneous docs |
| // (see https://github.com/google/protobuf/issues/1406); |
| printer->Print("/**\n"); |
| std::vector<string> lines = GrpcGetDocLinesForDescriptor(service); |
| GrpcWriteDocCommentBody(printer, lines, true); |
| printer->Print(" */\n"); |
| } |
| |
| // TODO(nmittler): Remove once protobuf includes javadoc methods in distribution. |
| void GrpcWriteMethodDocComment(Printer* printer, |
| const MethodDescriptor* method) { |
| // Deviating from protobuf to avoid extraneous docs |
| // (see https://github.com/google/protobuf/issues/1406); |
| printer->Print("/**\n"); |
| std::vector<string> lines = GrpcGetDocLinesForDescriptor(method); |
| GrpcWriteDocCommentBody(printer, lines, true); |
| printer->Print(" */\n"); |
| } |
| |
| static void PrintMethodFields( |
| const ServiceDescriptor* service, std::map<string, string>* vars, |
| Printer* p, ProtoFlavor flavor) { |
| p->Print("// Static method descriptors that strictly reflect the proto.\n"); |
| (*vars)["service_name"] = service->name(); |
| for (int i = 0; i < service->method_count(); ++i) { |
| const MethodDescriptor* method = service->method(i); |
| (*vars)["arg_in_id"] = to_string(2 * i); |
| (*vars)["arg_out_id"] = to_string(2 * i + 1); |
| (*vars)["method_name"] = method->name(); |
| (*vars)["input_type"] = MessageFullJavaName(flavor == ProtoFlavor::NANO, |
| method->input_type()); |
| (*vars)["output_type"] = MessageFullJavaName(flavor == ProtoFlavor::NANO, |
| method->output_type()); |
| (*vars)["method_field_name"] = MethodPropertiesFieldName(method); |
| (*vars)["method_new_field_name"] = MethodPropertiesGetterName(method); |
| (*vars)["method_method_name"] = MethodPropertiesGetterName(method); |
| (*vars)["method_method_name_helper"] = MethodPropertiesGetterHelperName(method); |
| bool client_streaming = method->client_streaming(); |
| bool server_streaming = method->server_streaming(); |
| if (client_streaming) { |
| if (server_streaming) { |
| (*vars)["method_type"] = "BIDI_STREAMING"; |
| } else { |
| (*vars)["method_type"] = "CLIENT_STREAMING"; |
| } |
| } else { |
| if (server_streaming) { |
| (*vars)["method_type"] = "SERVER_STREAMING"; |
| } else { |
| (*vars)["method_type"] = "UNARY"; |
| } |
| } |
| |
| if (flavor == ProtoFlavor::NANO) { |
| // TODO(zsurocking): we're creating two NanoFactories for each method right now. |
| // We could instead create static NanoFactories and reuse them if some methods |
| // share the same request or response messages. |
| p->Print( |
| *vars, |
| "private static final int ARG_IN_$method_field_name$ = $arg_in_id$;\n" |
| "private static final int ARG_OUT_$method_field_name$ = $arg_out_id$;\n" |
| "@$ExperimentalApi$(\"https://github.com/grpc/grpc-java/issues/1901\")\n" |
| "@$Deprecated$ // Use {@link #$method_method_name$()} instead. \n" |
| "public static final $MethodDescriptor$<$input_type$,\n" |
| " $output_type$> $method_field_name$ = $method_method_name_helper$();\n" |
| "\n" |
| "private static volatile $MethodDescriptor$<$input_type$,\n" |
| " $output_type$> $method_new_field_name$;\n" |
| "\n" |
| "@$ExperimentalApi$(\"https://github.com/grpc/grpc-java/issues/1901\")\n" |
| "public static $MethodDescriptor$<$input_type$,\n" |
| " $output_type$> $method_method_name$() {\n" |
| " return $method_method_name_helper$();\n" |
| "}\n" |
| "\n" |
| "private static $MethodDescriptor$<$input_type$,\n" |
| " $output_type$> $method_method_name_helper$() {\n" |
| " $MethodDescriptor$<$input_type$, $output_type$> $method_new_field_name$;\n" |
| " if (($method_new_field_name$ = $service_class_name$.$method_new_field_name$) == null) {\n" |
| " synchronized ($service_class_name$.class) {\n" |
| " if (($method_new_field_name$ = $service_class_name$.$method_new_field_name$) == null) {\n" |
| " $service_class_name$.$method_new_field_name$ = $method_new_field_name$ = \n" |
| " $MethodDescriptor$.<$input_type$, $output_type$>newBuilder()\n" |
| " .setType($MethodType$.$method_type$)\n" |
| " .setFullMethodName(generateFullMethodName(\n" |
| " \"$Package$$service_name$\", \"$method_name$\"))\n" |
| " .setSampledToLocalTracing(true)\n" |
| " .setRequestMarshaller($NanoUtils$.<$input_type$>marshaller(\n" |
| " new NanoFactory<$input_type$>(ARG_IN_$method_field_name$)))\n" |
| " .setResponseMarshaller($NanoUtils$.<$output_type$>marshaller(\n" |
| " new NanoFactory<$output_type$>(ARG_OUT_$method_field_name$)))\n" |
| " .build();\n" |
| " }\n" |
| " }\n" |
| " }\n" |
| " return $method_new_field_name$;\n" |
| "}\n"); |
| } else { |
| if (flavor == ProtoFlavor::LITE) { |
| (*vars)["ProtoUtils"] = "io.grpc.protobuf.lite.ProtoLiteUtils"; |
| } else { |
| (*vars)["ProtoUtils"] = "io.grpc.protobuf.ProtoUtils"; |
| } |
| p->Print( |
| *vars, |
| "@$ExperimentalApi$(\"https://github.com/grpc/grpc-java/issues/1901\")\n" |
| "@$Deprecated$ // Use {@link #$method_method_name$()} instead. \n" |
| "public static final $MethodDescriptor$<$input_type$,\n" |
| " $output_type$> $method_field_name$ = $method_method_name_helper$();\n" |
| "\n" |
| "private static volatile $MethodDescriptor$<$input_type$,\n" |
| " $output_type$> $method_new_field_name$;\n" |
| "\n" |
| "@$ExperimentalApi$(\"https://github.com/grpc/grpc-java/issues/1901\")\n" |
| "public static $MethodDescriptor$<$input_type$,\n" |
| " $output_type$> $method_method_name$() {\n" |
| " return $method_method_name_helper$();\n" |
| "}\n" |
| "\n" |
| "private static $MethodDescriptor$<$input_type$,\n" |
| " $output_type$> $method_method_name_helper$() {\n" |
| " $MethodDescriptor$<$input_type$, $output_type$> $method_new_field_name$;\n" |
| " if (($method_new_field_name$ = $service_class_name$.$method_new_field_name$) == null) {\n" |
| " synchronized ($service_class_name$.class) {\n" |
| " if (($method_new_field_name$ = $service_class_name$.$method_new_field_name$) == null) {\n" |
| " $service_class_name$.$method_new_field_name$ = $method_new_field_name$ = \n" |
| " $MethodDescriptor$.<$input_type$, $output_type$>newBuilder()\n" |
| " .setType($MethodType$.$method_type$)\n" |
| " .setFullMethodName(generateFullMethodName(\n" |
| " \"$Package$$service_name$\", \"$method_name$\"))\n" |
| " .setSampledToLocalTracing(true)\n" |
| " .setRequestMarshaller($ProtoUtils$.marshaller(\n" |
| " $input_type$.getDefaultInstance()))\n" |
| " .setResponseMarshaller($ProtoUtils$.marshaller(\n" |
| " $output_type$.getDefaultInstance()))\n"); |
| |
| (*vars)["proto_method_descriptor_supplier"] = service->name() + "MethodDescriptorSupplier"; |
| if (flavor == ProtoFlavor::NORMAL) { |
| p->Print( |
| *vars, |
| " .setSchemaDescriptor(new $proto_method_descriptor_supplier$(\"$method_name$\"))\n"); |
| } |
| |
| p->Print( |
| *vars, |
| " .build();\n"); |
| p->Print(*vars, |
| " }\n" |
| " }\n" |
| " }\n" |
| " return $method_new_field_name$;\n" |
| "}\n"); |
| |
| } |
| } |
| p->Print("\n"); |
| |
| if (flavor == ProtoFlavor::NANO) { |
| p->Print( |
| *vars, |
| "private static final class NanoFactory<T extends com.google.protobuf.nano.MessageNano>\n" |
| " implements io.grpc.protobuf.nano.MessageNanoFactory<T> {\n" |
| " private final int id;\n" |
| "\n" |
| " NanoFactory(int id) {\n" |
| " this.id = id;\n" |
| " }\n" |
| "\n" |
| " @$Override$\n" |
| " public T newInstance() {\n" |
| " Object o;\n" |
| " switch (id) {\n"); |
| bool generate_nano = true; |
| for (int i = 0; i < service->method_count(); ++i) { |
| const MethodDescriptor* method = service->method(i); |
| (*vars)["input_type"] = MessageFullJavaName(generate_nano, |
| method->input_type()); |
| (*vars)["output_type"] = MessageFullJavaName(generate_nano, |
| method->output_type()); |
| (*vars)["method_field_name"] = MethodPropertiesFieldName(method); |
| p->Print( |
| *vars, |
| " case ARG_IN_$method_field_name$:\n" |
| " o = new $input_type$();\n" |
| " break;\n" |
| " case ARG_OUT_$method_field_name$:\n" |
| " o = new $output_type$();\n" |
| " break;\n"); |
| } |
| p->Print( |
| " default:\n" |
| " throw new AssertionError();\n" |
| " }\n" |
| " @java.lang.SuppressWarnings(\"unchecked\")\n" |
| " T t = (T) o;\n" |
| " return t;\n" |
| " }\n" |
| "}\n" |
| "\n"); |
| } |
| } |
| |
| enum StubType { |
| ASYNC_INTERFACE = 0, |
| BLOCKING_CLIENT_INTERFACE = 1, |
| FUTURE_CLIENT_INTERFACE = 2, |
| BLOCKING_SERVER_INTERFACE = 3, |
| ASYNC_CLIENT_IMPL = 4, |
| BLOCKING_CLIENT_IMPL = 5, |
| FUTURE_CLIENT_IMPL = 6, |
| ABSTRACT_CLASS = 7, |
| }; |
| |
| enum CallType { |
| ASYNC_CALL = 0, |
| BLOCKING_CALL = 1, |
| FUTURE_CALL = 2 |
| }; |
| |
| static void PrintBindServiceMethodBody(const ServiceDescriptor* service, |
| std::map<string, string>* vars, |
| Printer* p, |
| bool generate_nano); |
| |
| // Prints a client interface or implementation class, or a server interface. |
| static void PrintStub( |
| const ServiceDescriptor* service, |
| std::map<string, string>* vars, |
| Printer* p, StubType type, bool generate_nano) { |
| const string service_name = service->name(); |
| (*vars)["service_name"] = service_name; |
| (*vars)["abstract_name"] = service_name + "ImplBase"; |
| string stub_name = service_name; |
| string client_name = service_name; |
| CallType call_type; |
| bool impl_base = false; |
| bool interface = false; |
| switch (type) { |
| case ABSTRACT_CLASS: |
| call_type = ASYNC_CALL; |
| impl_base = true; |
| break; |
| case ASYNC_CLIENT_IMPL: |
| call_type = ASYNC_CALL; |
| stub_name += "Stub"; |
| break; |
| case BLOCKING_CLIENT_INTERFACE: |
| interface = true; |
| FALLTHROUGH_INTENDED; |
| case BLOCKING_CLIENT_IMPL: |
| call_type = BLOCKING_CALL; |
| stub_name += "BlockingStub"; |
| client_name += "BlockingClient"; |
| break; |
| case FUTURE_CLIENT_INTERFACE: |
| interface = true; |
| FALLTHROUGH_INTENDED; |
| case FUTURE_CLIENT_IMPL: |
| call_type = FUTURE_CALL; |
| stub_name += "FutureStub"; |
| client_name += "FutureClient"; |
| break; |
| case ASYNC_INTERFACE: |
| call_type = ASYNC_CALL; |
| interface = true; |
| break; |
| default: |
| GRPC_CODEGEN_FAIL << "Cannot determine class name for StubType: " << type; |
| } |
| (*vars)["stub_name"] = stub_name; |
| (*vars)["client_name"] = client_name; |
| |
| // Class head |
| if (!interface) { |
| GrpcWriteServiceDocComment(p, service); |
| } |
| if (impl_base) { |
| p->Print( |
| *vars, |
| "public static abstract class $abstract_name$ implements $BindableService$ {\n"); |
| } else { |
| p->Print( |
| *vars, |
| "public static final class $stub_name$ extends $AbstractStub$<$stub_name$> {\n"); |
| } |
| p->Indent(); |
| |
| // Constructor and build() method |
| if (!impl_base && !interface) { |
| p->Print( |
| *vars, |
| "private $stub_name$($Channel$ channel) {\n"); |
| p->Indent(); |
| p->Print("super(channel);\n"); |
| p->Outdent(); |
| p->Print("}\n\n"); |
| p->Print( |
| *vars, |
| "private $stub_name$($Channel$ channel,\n" |
| " $CallOptions$ callOptions) {\n"); |
| p->Indent(); |
| p->Print("super(channel, callOptions);\n"); |
| p->Outdent(); |
| p->Print("}\n\n"); |
| p->Print( |
| *vars, |
| "@$Override$\n" |
| "protected $stub_name$ build($Channel$ channel,\n" |
| " $CallOptions$ callOptions) {\n"); |
| p->Indent(); |
| p->Print( |
| *vars, |
| "return new $stub_name$(channel, callOptions);\n"); |
| p->Outdent(); |
| p->Print("}\n"); |
| } |
| |
| // RPC methods |
| for (int i = 0; i < service->method_count(); ++i) { |
| const MethodDescriptor* method = service->method(i); |
| (*vars)["input_type"] = MessageFullJavaName(generate_nano, |
| method->input_type()); |
| (*vars)["output_type"] = MessageFullJavaName(generate_nano, |
| method->output_type()); |
| (*vars)["lower_method_name"] = LowerMethodName(method); |
| (*vars)["method_method_name_helper"] = MethodPropertiesGetterHelperName(method); |
| bool client_streaming = method->client_streaming(); |
| bool server_streaming = method->server_streaming(); |
| |
| if (call_type == BLOCKING_CALL && client_streaming) { |
| // Blocking client interface with client streaming is not available |
| continue; |
| } |
| |
| if (call_type == FUTURE_CALL && (client_streaming || server_streaming)) { |
| // Future interface doesn't support streaming. |
| continue; |
| } |
| |
| // Method signature |
| p->Print("\n"); |
| // TODO(nmittler): Replace with WriteMethodDocComment once included by the protobuf distro. |
| if (!interface) { |
| GrpcWriteMethodDocComment(p, method); |
| } |
| p->Print("public "); |
| switch (call_type) { |
| case BLOCKING_CALL: |
| GRPC_CODEGEN_CHECK(!client_streaming) |
| << "Blocking client interface with client streaming is unavailable"; |
| if (server_streaming) { |
| // Server streaming |
| p->Print( |
| *vars, |
| "$Iterator$<$output_type$> $lower_method_name$(\n" |
| " $input_type$ request)"); |
| } else { |
| // Simple RPC |
| p->Print( |
| *vars, |
| "$output_type$ $lower_method_name$($input_type$ request)"); |
| } |
| break; |
| case ASYNC_CALL: |
| if (client_streaming) { |
| // Bidirectional streaming or client streaming |
| p->Print( |
| *vars, |
| "$StreamObserver$<$input_type$> $lower_method_name$(\n" |
| " $StreamObserver$<$output_type$> responseObserver)"); |
| } else { |
| // Server streaming or simple RPC |
| p->Print( |
| *vars, |
| "void $lower_method_name$($input_type$ request,\n" |
| " $StreamObserver$<$output_type$> responseObserver)"); |
| } |
| break; |
| case FUTURE_CALL: |
| GRPC_CODEGEN_CHECK(!client_streaming && !server_streaming) |
| << "Future interface doesn't support streaming. " |
| << "client_streaming=" << client_streaming << ", " |
| << "server_streaming=" << server_streaming; |
| p->Print( |
| *vars, |
| "$ListenableFuture$<$output_type$> $lower_method_name$(\n" |
| " $input_type$ request)"); |
| break; |
| } |
| |
| if (interface) { |
| p->Print(";\n"); |
| continue; |
| } |
| // Method body. |
| p->Print(" {\n"); |
| p->Indent(); |
| if (impl_base) { |
| switch (call_type) { |
| // NB: Skipping validation of service methods. If something is wrong, we wouldn't get to |
| // this point as compiler would return errors when generating service interface. |
| case ASYNC_CALL: |
| if (client_streaming) { |
| p->Print( |
| *vars, |
| "return asyncUnimplementedStreamingCall($method_method_name_helper$(), responseObserver);\n"); |
| } else { |
| p->Print( |
| *vars, |
| "asyncUnimplementedUnaryCall($method_method_name_helper$(), responseObserver);\n"); |
| } |
| break; |
| default: |
| break; |
| } |
| } else if (!interface) { |
| switch (call_type) { |
| case BLOCKING_CALL: |
| GRPC_CODEGEN_CHECK(!client_streaming) |
| << "Blocking client streaming interface is not available"; |
| if (server_streaming) { |
| (*vars)["calls_method"] = "blockingServerStreamingCall"; |
| (*vars)["params"] = "request"; |
| } else { |
| (*vars)["calls_method"] = "blockingUnaryCall"; |
| (*vars)["params"] = "request"; |
| } |
| p->Print( |
| *vars, |
| "return $calls_method$(\n" |
| " getChannel(), $method_method_name_helper$(), getCallOptions(), $params$);\n"); |
| break; |
| case ASYNC_CALL: |
| if (server_streaming) { |
| if (client_streaming) { |
| (*vars)["calls_method"] = "asyncBidiStreamingCall"; |
| (*vars)["params"] = "responseObserver"; |
| } else { |
| (*vars)["calls_method"] = "asyncServerStreamingCall"; |
| (*vars)["params"] = "request, responseObserver"; |
| } |
| } else { |
| if (client_streaming) { |
| (*vars)["calls_method"] = "asyncClientStreamingCall"; |
| (*vars)["params"] = "responseObserver"; |
| } else { |
| (*vars)["calls_method"] = "asyncUnaryCall"; |
| (*vars)["params"] = "request, responseObserver"; |
| } |
| } |
| (*vars)["last_line_prefix"] = client_streaming ? "return " : ""; |
| p->Print( |
| *vars, |
| "$last_line_prefix$$calls_method$(\n" |
| " getChannel().newCall($method_method_name_helper$(), getCallOptions()), $params$);\n"); |
| break; |
| case FUTURE_CALL: |
| GRPC_CODEGEN_CHECK(!client_streaming && !server_streaming) |
| << "Future interface doesn't support streaming. " |
| << "client_streaming=" << client_streaming << ", " |
| << "server_streaming=" << server_streaming; |
| (*vars)["calls_method"] = "futureUnaryCall"; |
| p->Print( |
| *vars, |
| "return $calls_method$(\n" |
| " getChannel().newCall($method_method_name_helper$(), getCallOptions()), request);\n"); |
| break; |
| } |
| } |
| p->Outdent(); |
| p->Print("}\n"); |
| } |
| |
| if (impl_base) { |
| p->Print("\n"); |
| p->Print( |
| *vars, |
| "@$Override$ public final $ServerServiceDefinition$ bindService() {\n"); |
| (*vars)["instance"] = "this"; |
| PrintBindServiceMethodBody(service, vars, p, generate_nano); |
| p->Print("}\n"); |
| } |
| |
| p->Outdent(); |
| p->Print("}\n\n"); |
| } |
| |
| static bool CompareMethodClientStreaming(const MethodDescriptor* method1, |
| const MethodDescriptor* method2) |
| { |
| return method1->client_streaming() < method2->client_streaming(); |
| } |
| |
| // Place all method invocations into a single class to reduce memory footprint |
| // on Android. |
| static void PrintMethodHandlerClass(const ServiceDescriptor* service, |
| std::map<string, string>* vars, |
| Printer* p, |
| bool generate_nano) { |
| // Sort method ids based on client_streaming() so switch tables are compact. |
| std::vector<const MethodDescriptor*> sorted_methods(service->method_count()); |
| for (int i = 0; i < service->method_count(); ++i) { |
| sorted_methods[i] = service->method(i); |
| } |
| stable_sort(sorted_methods.begin(), sorted_methods.end(), |
| CompareMethodClientStreaming); |
| for (size_t i = 0; i < sorted_methods.size(); i++) { |
| const MethodDescriptor* method = sorted_methods[i]; |
| (*vars)["method_id"] = to_string(i); |
| (*vars)["method_id_name"] = MethodIdFieldName(method); |
| p->Print( |
| *vars, |
| "private static final int $method_id_name$ = $method_id$;\n"); |
| } |
| p->Print("\n"); |
| (*vars)["service_name"] = service->name() + "ImplBase"; |
| p->Print( |
| *vars, |
| "private static final class MethodHandlers<Req, Resp> implements\n" |
| " io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,\n" |
| " io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,\n" |
| " io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,\n" |
| " io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {\n" |
| " private final $service_name$ serviceImpl;\n" |
| " private final int methodId;\n" |
| "\n" |
| " MethodHandlers($service_name$ serviceImpl, int methodId) {\n" |
| " this.serviceImpl = serviceImpl;\n" |
| " this.methodId = methodId;\n" |
| " }\n\n"); |
| p->Indent(); |
| p->Print( |
| *vars, |
| "@$Override$\n" |
| "@java.lang.SuppressWarnings(\"unchecked\")\n" |
| "public void invoke(Req request, $StreamObserver$<Resp> responseObserver) {\n" |
| " switch (methodId) {\n"); |
| p->Indent(); |
| p->Indent(); |
| |
| for (int i = 0; i < service->method_count(); ++i) { |
| const MethodDescriptor* method = service->method(i); |
| if (method->client_streaming()) { |
| continue; |
| } |
| (*vars)["method_id_name"] = MethodIdFieldName(method); |
| (*vars)["lower_method_name"] = LowerMethodName(method); |
| (*vars)["input_type"] = MessageFullJavaName(generate_nano, |
| method->input_type()); |
| (*vars)["output_type"] = MessageFullJavaName(generate_nano, |
| method->output_type()); |
| p->Print( |
| *vars, |
| "case $method_id_name$:\n" |
| " serviceImpl.$lower_method_name$(($input_type$) request,\n" |
| " ($StreamObserver$<$output_type$>) responseObserver);\n" |
| " break;\n"); |
| } |
| p->Print("default:\n" |
| " throw new AssertionError();\n"); |
| |
| p->Outdent(); |
| p->Outdent(); |
| p->Print(" }\n" |
| "}\n\n"); |
| |
| p->Print( |
| *vars, |
| "@$Override$\n" |
| "@java.lang.SuppressWarnings(\"unchecked\")\n" |
| "public $StreamObserver$<Req> invoke(\n" |
| " $StreamObserver$<Resp> responseObserver) {\n" |
| " switch (methodId) {\n"); |
| p->Indent(); |
| p->Indent(); |
| |
| for (int i = 0; i < service->method_count(); ++i) { |
| const MethodDescriptor* method = service->method(i); |
| if (!method->client_streaming()) { |
| continue; |
| } |
| (*vars)["method_id_name"] = MethodIdFieldName(method); |
| (*vars)["lower_method_name"] = LowerMethodName(method); |
| (*vars)["input_type"] = MessageFullJavaName(generate_nano, |
| method->input_type()); |
| (*vars)["output_type"] = MessageFullJavaName(generate_nano, |
| method->output_type()); |
| p->Print( |
| *vars, |
| "case $method_id_name$:\n" |
| " return ($StreamObserver$<Req>) serviceImpl.$lower_method_name$(\n" |
| " ($StreamObserver$<$output_type$>) responseObserver);\n"); |
| } |
| p->Print("default:\n" |
| " throw new AssertionError();\n"); |
| |
| p->Outdent(); |
| p->Outdent(); |
| p->Print(" }\n" |
| "}\n"); |
| |
| |
| p->Outdent(); |
| p->Print("}\n\n"); |
| } |
| |
| static void PrintGetServiceDescriptorMethod(const ServiceDescriptor* service, |
| std::map<string, string>* vars, |
| Printer* p, |
| ProtoFlavor flavor) { |
| (*vars)["service_name"] = service->name(); |
| |
| |
| if (flavor == ProtoFlavor::NORMAL) { |
| (*vars)["proto_base_descriptor_supplier"] = service->name() + "BaseDescriptorSupplier"; |
| (*vars)["proto_file_descriptor_supplier"] = service->name() + "FileDescriptorSupplier"; |
| (*vars)["proto_method_descriptor_supplier"] = service->name() + "MethodDescriptorSupplier"; |
| (*vars)["proto_class_name"] = google::protobuf::compiler::java::ClassName(service->file()); |
| p->Print( |
| *vars, |
| "private static abstract class $proto_base_descriptor_supplier$\n" |
| " implements $ProtoFileDescriptorSupplier$, $ProtoServiceDescriptorSupplier$ {\n" |
| " $proto_base_descriptor_supplier$() {}\n" |
| "\n" |
| " @$Override$\n" |
| " public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {\n" |
| " return $proto_class_name$.getDescriptor();\n" |
| " }\n" |
| "\n" |
| " @$Override$\n" |
| " public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {\n" |
| " return getFileDescriptor().findServiceByName(\"$service_name$\");\n" |
| " }\n" |
| "}\n" |
| "\n" |
| "private static final class $proto_file_descriptor_supplier$\n" |
| " extends $proto_base_descriptor_supplier$ {\n" |
| " $proto_file_descriptor_supplier$() {}\n" |
| "}\n" |
| "\n" |
| "private static final class $proto_method_descriptor_supplier$\n" |
| " extends $proto_base_descriptor_supplier$\n" |
| " implements $ProtoMethodDescriptorSupplier$ {\n" |
| " private final String methodName;\n" |
| "\n" |
| " $proto_method_descriptor_supplier$(String methodName) {\n" |
| " this.methodName = methodName;\n" |
| " }\n" |
| "\n" |
| " @$Override$\n" |
| " public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {\n" |
| " return getServiceDescriptor().findMethodByName(methodName);\n" |
| " }\n" |
| "}\n\n"); |
| } |
| |
| p->Print( |
| *vars, |
| "private static volatile $ServiceDescriptor$ serviceDescriptor;\n\n"); |
| |
| p->Print( |
| *vars, |
| "public static $ServiceDescriptor$ getServiceDescriptor() {\n"); |
| p->Indent(); |
| p->Print( |
| *vars, |
| "$ServiceDescriptor$ result = serviceDescriptor;\n"); |
| p->Print("if (result == null) {\n"); |
| p->Indent(); |
| p->Print( |
| *vars, |
| "synchronized ($service_class_name$.class) {\n"); |
| p->Indent(); |
| p->Print("result = serviceDescriptor;\n"); |
| p->Print("if (result == null) {\n"); |
| p->Indent(); |
| |
| p->Print( |
| *vars, |
| "serviceDescriptor = result = $ServiceDescriptor$.newBuilder(SERVICE_NAME)"); |
| p->Indent(); |
| p->Indent(); |
| if (flavor == ProtoFlavor::NORMAL) { |
| p->Print( |
| *vars, |
| "\n.setSchemaDescriptor(new $proto_file_descriptor_supplier$())"); |
| } |
| for (int i = 0; i < service->method_count(); ++i) { |
| const MethodDescriptor* method = service->method(i); |
| (*vars)["method_method_name_helper"] = MethodPropertiesGetterHelperName(method); |
| p->Print(*vars, "\n.addMethod($method_method_name_helper$())"); |
| } |
| p->Print("\n.build();\n"); |
| p->Outdent(); |
| p->Outdent(); |
| |
| p->Outdent(); |
| p->Print("}\n"); |
| p->Outdent(); |
| p->Print("}\n"); |
| p->Outdent(); |
| p->Print("}\n"); |
| p->Print("return result;\n"); |
| p->Outdent(); |
| p->Print("}\n"); |
| } |
| |
| static void PrintBindServiceMethodBody(const ServiceDescriptor* service, |
| std::map<string, string>* vars, |
| Printer* p, |
| bool generate_nano) { |
| (*vars)["service_name"] = service->name(); |
| p->Indent(); |
| p->Print(*vars, |
| "return " |
| "$ServerServiceDefinition$.builder(getServiceDescriptor())\n"); |
| p->Indent(); |
| p->Indent(); |
| for (int i = 0; i < service->method_count(); ++i) { |
| const MethodDescriptor* method = service->method(i); |
| (*vars)["lower_method_name"] = LowerMethodName(method); |
| (*vars)["method_method_name_helper"] = MethodPropertiesGetterHelperName(method); |
| (*vars)["input_type"] = MessageFullJavaName(generate_nano, |
| method->input_type()); |
| (*vars)["output_type"] = MessageFullJavaName(generate_nano, |
| method->output_type()); |
| (*vars)["method_id_name"] = MethodIdFieldName(method); |
| bool client_streaming = method->client_streaming(); |
| bool server_streaming = method->server_streaming(); |
| if (client_streaming) { |
| if (server_streaming) { |
| (*vars)["calls_method"] = "asyncBidiStreamingCall"; |
| } else { |
| (*vars)["calls_method"] = "asyncClientStreamingCall"; |
| } |
| } else { |
| if (server_streaming) { |
| (*vars)["calls_method"] = "asyncServerStreamingCall"; |
| } else { |
| (*vars)["calls_method"] = "asyncUnaryCall"; |
| } |
| } |
| p->Print(*vars, ".addMethod(\n"); |
| p->Indent(); |
| p->Print( |
| *vars, |
| "$method_method_name_helper$(),\n" |
| "$calls_method$(\n"); |
| p->Indent(); |
| p->Print( |
| *vars, |
| "new MethodHandlers<\n" |
| " $input_type$,\n" |
| " $output_type$>(\n" |
| " $instance$, $method_id_name$)))\n"); |
| p->Outdent(); |
| p->Outdent(); |
| } |
| p->Print(".build();\n"); |
| p->Outdent(); |
| p->Outdent(); |
| p->Outdent(); |
| } |
| |
| static void PrintService(const ServiceDescriptor* service, |
| std::map<string, string>* vars, |
| Printer* p, |
| ProtoFlavor flavor, |
| bool disable_version) { |
| (*vars)["service_name"] = service->name(); |
| (*vars)["file_name"] = service->file()->name(); |
| (*vars)["service_class_name"] = ServiceClassName(service); |
| (*vars)["grpc_version"] = ""; |
| #ifdef GRPC_VERSION |
| if (!disable_version) { |
| (*vars)["grpc_version"] = " (version " XSTR(GRPC_VERSION) ")"; |
| } |
| #endif |
| // TODO(nmittler): Replace with WriteServiceDocComment once included by protobuf distro. |
| GrpcWriteServiceDocComment(p, service); |
| p->Print( |
| *vars, |
| "@$Generated$(\n" |
| " value = \"by gRPC proto compiler$grpc_version$\",\n" |
| " comments = \"Source: $file_name$\")\n" |
| "public final class $service_class_name$ {\n\n"); |
| p->Indent(); |
| p->Print( |
| *vars, |
| "private $service_class_name$() {}\n\n"); |
| |
| p->Print( |
| *vars, |
| "public static final String SERVICE_NAME = " |
| "\"$Package$$service_name$\";\n\n"); |
| |
| PrintMethodFields(service, vars, p, flavor); |
| |
| // TODO(nmittler): Replace with WriteDocComment once included by protobuf distro. |
| GrpcWriteDocComment(p, " Creates a new async stub that supports all call types for the service"); |
| p->Print( |
| *vars, |
| "public static $service_name$Stub newStub($Channel$ channel) {\n"); |
| p->Indent(); |
| p->Print( |
| *vars, |
| "return new $service_name$Stub(channel);\n"); |
| p->Outdent(); |
| p->Print("}\n\n"); |
| |
| // TODO(nmittler): Replace with WriteDocComment once included by protobuf distro. |
| GrpcWriteDocComment(p, " Creates a new blocking-style stub that supports unary and streaming " |
| "output calls on the service"); |
| p->Print( |
| *vars, |
| "public static $service_name$BlockingStub newBlockingStub(\n" |
| " $Channel$ channel) {\n"); |
| p->Indent(); |
| p->Print( |
| *vars, |
| "return new $service_name$BlockingStub(channel);\n"); |
| p->Outdent(); |
| p->Print("}\n\n"); |
| |
| // TODO(nmittler): Replace with WriteDocComment once included by protobuf distro. |
| GrpcWriteDocComment(p, " Creates a new ListenableFuture-style stub that supports unary calls " |
| "on the service"); |
| p->Print( |
| *vars, |
| "public static $service_name$FutureStub newFutureStub(\n" |
| " $Channel$ channel) {\n"); |
| p->Indent(); |
| p->Print( |
| *vars, |
| "return new $service_name$FutureStub(channel);\n"); |
| p->Outdent(); |
| p->Print("}\n\n"); |
| |
| bool generate_nano = flavor == ProtoFlavor::NANO; |
| PrintStub(service, vars, p, ABSTRACT_CLASS, generate_nano); |
| PrintStub(service, vars, p, ASYNC_CLIENT_IMPL, generate_nano); |
| PrintStub(service, vars, p, BLOCKING_CLIENT_IMPL, generate_nano); |
| PrintStub(service, vars, p, FUTURE_CLIENT_IMPL, generate_nano); |
| |
| PrintMethodHandlerClass(service, vars, p, generate_nano); |
| PrintGetServiceDescriptorMethod(service, vars, p, flavor); |
| p->Outdent(); |
| p->Print("}\n"); |
| } |
| |
| void PrintImports(Printer* p, bool generate_nano) { |
| p->Print( |
| "import static " |
| "io.grpc.MethodDescriptor.generateFullMethodName;\n" |
| "import static " |
| "io.grpc.stub.ClientCalls.asyncBidiStreamingCall;\n" |
| "import static " |
| "io.grpc.stub.ClientCalls.asyncClientStreamingCall;\n" |
| "import static " |
| "io.grpc.stub.ClientCalls.asyncServerStreamingCall;\n" |
| "import static " |
| "io.grpc.stub.ClientCalls.asyncUnaryCall;\n" |
| "import static " |
| "io.grpc.stub.ClientCalls.blockingServerStreamingCall;\n" |
| "import static " |
| "io.grpc.stub.ClientCalls.blockingUnaryCall;\n" |
| "import static " |
| "io.grpc.stub.ClientCalls.futureUnaryCall;\n" |
| "import static " |
| "io.grpc.stub.ServerCalls.asyncBidiStreamingCall;\n" |
| "import static " |
| "io.grpc.stub.ServerCalls.asyncClientStreamingCall;\n" |
| "import static " |
| "io.grpc.stub.ServerCalls.asyncServerStreamingCall;\n" |
| "import static " |
| "io.grpc.stub.ServerCalls.asyncUnaryCall;\n" |
| "import static " |
| "io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;\n" |
| "import static " |
| "io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;\n\n"); |
| if (generate_nano) { |
| p->Print("import java.io.IOException;\n\n"); |
| } |
| } |
| |
| void GenerateService(const ServiceDescriptor* service, |
| google::protobuf::io::ZeroCopyOutputStream* out, |
| ProtoFlavor flavor, |
| bool disable_version) { |
| // All non-generated classes must be referred by fully qualified names to |
| // avoid collision with generated classes. |
| std::map<string, string> vars; |
| vars["String"] = "java.lang.String"; |
| vars["Deprecated"] = "java.lang.Deprecated"; |
| vars["Override"] = "java.lang.Override"; |
| vars["Channel"] = "io.grpc.Channel"; |
| vars["CallOptions"] = "io.grpc.CallOptions"; |
| vars["MethodType"] = "io.grpc.MethodDescriptor.MethodType"; |
| vars["ServerMethodDefinition"] = |
| "io.grpc.ServerMethodDefinition"; |
| vars["BindableService"] = "io.grpc.BindableService"; |
| vars["ServerServiceDefinition"] = |
| "io.grpc.ServerServiceDefinition"; |
| vars["ServiceDescriptor"] = |
| "io.grpc.ServiceDescriptor"; |
| vars["ProtoFileDescriptorSupplier"] = |
| "io.grpc.protobuf.ProtoFileDescriptorSupplier"; |
| vars["ProtoServiceDescriptorSupplier"] = |
| "io.grpc.protobuf.ProtoServiceDescriptorSupplier"; |
| vars["ProtoMethodDescriptorSupplier"] = |
| "io.grpc.protobuf.ProtoMethodDescriptorSupplier"; |
| vars["AbstractStub"] = "io.grpc.stub.AbstractStub"; |
| vars["MethodDescriptor"] = "io.grpc.MethodDescriptor"; |
| vars["NanoUtils"] = "io.grpc.protobuf.nano.NanoUtils"; |
| vars["StreamObserver"] = "io.grpc.stub.StreamObserver"; |
| vars["Iterator"] = "java.util.Iterator"; |
| vars["Generated"] = "javax.annotation.Generated"; |
| vars["ListenableFuture"] = |
| "com.google.common.util.concurrent.ListenableFuture"; |
| vars["ExperimentalApi"] = "io.grpc.ExperimentalApi"; |
| |
| Printer printer(out, '$'); |
| string package_name = ServiceJavaPackage(service->file(), |
| flavor == ProtoFlavor::NANO); |
| if (!package_name.empty()) { |
| printer.Print( |
| "package $package_name$;\n\n", |
| "package_name", package_name); |
| } |
| PrintImports(&printer, flavor == ProtoFlavor::NANO); |
| |
| // Package string is used to fully qualify method names. |
| vars["Package"] = service->file()->package(); |
| if (!vars["Package"].empty()) { |
| vars["Package"].append("."); |
| } |
| PrintService(service, &vars, &printer, flavor, disable_version); |
| } |
| |
| string ServiceJavaPackage(const FileDescriptor* file, bool nano) { |
| string result = google::protobuf::compiler::java::ClassName(file); |
| size_t last_dot_pos = result.find_last_of('.'); |
| if (last_dot_pos != string::npos) { |
| result.resize(last_dot_pos); |
| } else { |
| result = ""; |
| } |
| if (nano) { |
| if (!result.empty()) { |
| result += "."; |
| } |
| result += "nano"; |
| } |
| return result; |
| } |
| |
| string ServiceClassName(const google::protobuf::ServiceDescriptor* service) { |
| return service->name() + "Grpc"; |
| } |
| |
| } // namespace java_grpc_generator |