| /* | |
| * Copyright 2016 Google Inc. All rights reserved. | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "src/compiler/java_generator.h" | |
| #include <algorithm> | |
| #include <iostream> | |
| #include <iterator> | |
| #include <map> | |
| #include <utility> | |
| #include <vector> | |
| #include "flatbuffers/util.h" | |
| #define to_string flatbuffers::NumToString | |
| // Stringify helpers used solely to cast GRPC_VERSION | |
| #ifndef STR | |
| #define STR(s) #s | |
| #endif | |
| #ifndef XSTR | |
| #define XSTR(s) STR(s) | |
| #endif | |
| typedef grpc_generator::Printer Printer; | |
| typedef std::map<grpc::string, grpc::string> VARS; | |
| typedef grpc_generator::Service ServiceDescriptor; | |
| typedef grpc_generator::CommentHolder | |
| DescriptorType; // base class of all 'descriptors' | |
| typedef grpc_generator::Method MethodDescriptor; | |
| namespace grpc_java_generator { | |
| typedef std::string string; | |
| namespace { | |
| // Generates imports for the service | |
| static void GenerateImports(grpc_generator::File* file, | |
| grpc_generator::Printer* printer, VARS& vars) { | |
| vars["filename"] = file->filename(); | |
| printer->Print(vars, | |
| "//Generated by flatc compiler (version $flatc_version$)\n"); | |
| printer->Print("//If you make any local changes, they will be lost\n"); | |
| printer->Print(vars, "//source: $filename$.fbs\n\n"); | |
| printer->Print(vars, "package $Package$;\n\n"); | |
| vars["Package"] = vars["Package"] + "."; | |
| if (!file->additional_headers().empty()) { | |
| printer->Print(file->additional_headers().c_str()); | |
| printer->Print("\n\n"); | |
| } | |
| } | |
| // 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 += static_cast<string::value_type>(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 ? static_cast<string::value_type>(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 += static_cast<string::value_type>(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 MethodIdFieldName(const MethodDescriptor* method) { | |
| return "METHODID_" + ToAllUpperCase(method->name()); | |
| } | |
| static inline string JavaClassName(VARS& vars, const string& name) { | |
| // string name = google::protobuf::compiler::java::ClassName(desc); | |
| return vars["Package"] + name; | |
| } | |
| static inline string ServiceClassName(const string& service_name) { | |
| return service_name + "Grpc"; | |
| } | |
| // 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); | |
| } | |
| } | |
| 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); | |
| } | |
| 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; | |
| } | |
| 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>(); | |
| } | |
| static std::vector<string> GrpcGetDocLinesForDescriptor( | |
| const DescriptorType* descriptor) { | |
| return descriptor->GetAllComments(); | |
| // return GrpcGetDocLines(descriptor->GetLeadingComments("///")); | |
| } | |
| static void GrpcWriteDocCommentBody(Printer* printer, VARS& vars, | |
| 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. | |
| vars["line"] = lines[i]; | |
| if (!lines[i].empty() && lines[i][0] == '/') { | |
| printer->Print(vars, " * $line$\n"); | |
| } else { | |
| printer->Print(vars, " *$line$\n"); | |
| } | |
| } | |
| if (surroundWithPreTag) { | |
| printer->Print(" * </pre>\n"); | |
| } | |
| } | |
| } | |
| static void GrpcWriteDocComment(Printer* printer, VARS& vars, | |
| const string& comments) { | |
| printer->Print("/**\n"); | |
| std::vector<string> lines = GrpcGetDocLines(comments); | |
| GrpcWriteDocCommentBody(printer, vars, lines, false); | |
| printer->Print(" */\n"); | |
| } | |
| static void GrpcWriteServiceDocComment(Printer* printer, VARS& vars, | |
| const ServiceDescriptor* service) { | |
| printer->Print("/**\n"); | |
| std::vector<string> lines = GrpcGetDocLinesForDescriptor(service); | |
| GrpcWriteDocCommentBody(printer, vars, lines, true); | |
| printer->Print(" */\n"); | |
| } | |
| static void GrpcWriteMethodDocComment(Printer* printer, VARS& vars, | |
| const MethodDescriptor* method) { | |
| printer->Print("/**\n"); | |
| std::vector<string> lines = GrpcGetDocLinesForDescriptor(method); | |
| GrpcWriteDocCommentBody(printer, vars, lines, true); | |
| printer->Print(" */\n"); | |
| } | |
| // outputs static singleton extractor for type stored in "extr_type" and | |
| // "extr_type_name" vars | |
| static void PrintTypeExtractor(Printer* p, VARS& vars) { | |
| p->Print(vars, | |
| "private static volatile FlatbuffersUtils.FBExtactor<$extr_type$> " | |
| "extractorOf$extr_type_name$;\n" | |
| "private static FlatbuffersUtils.FBExtactor<$extr_type$> " | |
| "getExtractorOf$extr_type_name$() {\n" | |
| " if (extractorOf$extr_type_name$ != null) return " | |
| "extractorOf$extr_type_name$;\n" | |
| " synchronized ($service_class_name$.class) {\n" | |
| " if (extractorOf$extr_type_name$ != null) return " | |
| "extractorOf$extr_type_name$;\n" | |
| " extractorOf$extr_type_name$ = new " | |
| "FlatbuffersUtils.FBExtactor<$extr_type$>() {\n" | |
| " public $extr_type$ extract (ByteBuffer buffer) {\n" | |
| " return " | |
| "$extr_type$.getRootAs$extr_type_name$(buffer);\n" | |
| " }\n" | |
| " };\n" | |
| " return extractorOf$extr_type_name$;\n" | |
| " }\n" | |
| "}\n\n"); | |
| } | |
| static void PrintMethodFields(Printer* p, VARS& vars, | |
| const ServiceDescriptor* service) { | |
| p->Print("// Static method descriptors that strictly reflect the proto.\n"); | |
| vars["service_name"] = service->name(); | |
| // set of names of rpc input- and output- types that were already encountered. | |
| // this is needed to avoid duplicating type extractor since it's possible that | |
| // the same type is used as an input or output type of more than a single RPC | |
| // method | |
| std::set<std::string> encounteredTypes; | |
| for (int i = 0; i < service->method_count(); ++i) { | |
| auto method = service->method(i); | |
| vars["arg_in_id"] = to_string(2L * i); // trying to make msvc 10 happy | |
| vars["arg_out_id"] = to_string(2L * i + 1); | |
| vars["method_name"] = method->name(); | |
| vars["input_type_name"] = method->get_input_type_name(); | |
| vars["output_type_name"] = method->get_output_type_name(); | |
| vars["input_type"] = JavaClassName(vars, method->get_input_type_name()); | |
| vars["output_type"] = JavaClassName(vars, method->get_output_type_name()); | |
| vars["method_field_name"] = MethodPropertiesFieldName(method.get()); | |
| vars["method_new_field_name"] = MethodPropertiesGetterName(method.get()); | |
| vars["method_method_name"] = MethodPropertiesGetterName(method.get()); | |
| bool client_streaming = | |
| method->ClientStreaming() || method->BidiStreaming(); | |
| bool server_streaming = | |
| method->ServerStreaming() || method->BidiStreaming(); | |
| 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"; | |
| } | |
| } | |
| 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$();\n" | |
| "\n" | |
| "private static volatile $MethodDescriptor$<$input_type$,\n" | |
| " $output_type$> $method_new_field_name$;\n" | |
| "\n"); | |
| if (encounteredTypes.insert(vars["input_type_name"]).second) { | |
| vars["extr_type"] = vars["input_type"]; | |
| vars["extr_type_name"] = vars["input_type_name"]; | |
| PrintTypeExtractor(p, vars); | |
| } | |
| if (encounteredTypes.insert(vars["output_type_name"]).second) { | |
| vars["extr_type"] = vars["output_type"]; | |
| vars["extr_type_name"] = vars["output_type_name"]; | |
| PrintTypeExtractor(p, vars); | |
| } | |
| p->Print( | |
| vars, | |
| "@$ExperimentalApi$(\"https://github.com/grpc/grpc-java/issues/" | |
| "1901\")\n" | |
| "public static $MethodDescriptor$<$input_type$,\n" | |
| " $output_type$> $method_method_name$() {\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(FlatbuffersUtils.marshaller(\n" | |
| " $input_type$.class, " | |
| "getExtractorOf$input_type_name$()))\n" | |
| " .setResponseMarshaller(FlatbuffersUtils.marshaller(\n" | |
| " $output_type$.class, " | |
| "getExtractorOf$output_type_name$()))\n"); | |
| // vars["proto_method_descriptor_supplier"] = service->name() + | |
| // "MethodDescriptorSupplier"; | |
| p->Print(vars, " .setSchemaDescriptor(null)\n"); | |
| //" .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"); | |
| } | |
| } | |
| 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(Printer* p, VARS& vars, | |
| const ServiceDescriptor* service); | |
| // Prints a client interface or implementation class, or a server interface. | |
| static void PrintStub(Printer* p, VARS& vars, const ServiceDescriptor* service, | |
| StubType type) { | |
| 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 = ASYNC_CALL; | |
| 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; | |
| FLATBUFFERS_FALLTHROUGH(); // fall thru | |
| case BLOCKING_CLIENT_IMPL: | |
| call_type = BLOCKING_CALL; | |
| stub_name += "BlockingStub"; | |
| client_name += "BlockingClient"; | |
| break; | |
| case FUTURE_CLIENT_INTERFACE: | |
| interface = true; | |
| FLATBUFFERS_FALLTHROUGH(); // fall thru | |
| 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, vars, 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) { | |
| auto method = service->method(i); | |
| vars["input_type"] = JavaClassName(vars, method->get_input_type_name()); | |
| vars["output_type"] = JavaClassName(vars, method->get_output_type_name()); | |
| vars["lower_method_name"] = LowerMethodName(&*method); | |
| vars["method_method_name"] = MethodPropertiesGetterName(&*method); | |
| bool client_streaming = | |
| method->ClientStreaming() || method->BidiStreaming(); | |
| bool server_streaming = | |
| method->ServerStreaming() || method->BidiStreaming(); | |
| 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, vars, &*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$(), " | |
| "responseObserver);\n"); | |
| } else { | |
| p->Print(vars, | |
| "asyncUnimplementedUnaryCall($method_method_name$(), " | |
| "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$(), " | |
| "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$(), " | |
| "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$(), " | |
| "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(p, vars, service); | |
| p->Print("}\n"); | |
| } | |
| p->Outdent(); | |
| p->Print("}\n\n"); | |
| } | |
| static bool CompareMethodClientStreaming( | |
| const std::unique_ptr<const grpc_generator::Method>& method1, | |
| const std::unique_ptr<const grpc_generator::Method>& method2) { | |
| return method1->ClientStreaming() < method2->ClientStreaming(); | |
| } | |
| // Place all method invocations into a single class to reduce memory footprint | |
| // on Android. | |
| static void PrintMethodHandlerClass(Printer* p, VARS& vars, | |
| const ServiceDescriptor* service) { | |
| // Sort method ids based on ClientStreaming() so switch tables are compact. | |
| std::vector<std::unique_ptr<const grpc_generator::Method>> 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++) { | |
| auto& 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) { | |
| auto method = service->method(i); | |
| if (method->ClientStreaming() || method->BidiStreaming()) { | |
| continue; | |
| } | |
| vars["method_id_name"] = MethodIdFieldName(&*method); | |
| vars["lower_method_name"] = LowerMethodName(&*method); | |
| vars["input_type"] = JavaClassName(vars, method->get_input_type_name()); | |
| vars["output_type"] = JavaClassName(vars, method->get_output_type_name()); | |
| 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) { | |
| auto method = service->method(i); | |
| if (!(method->ClientStreaming() || method->BidiStreaming())) { | |
| continue; | |
| } | |
| vars["method_id_name"] = MethodIdFieldName(&*method); | |
| vars["lower_method_name"] = LowerMethodName(&*method); | |
| vars["input_type"] = JavaClassName(vars, method->get_input_type_name()); | |
| vars["output_type"] = JavaClassName(vars, method->get_output_type_name()); | |
| 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(Printer* p, VARS& vars, | |
| const ServiceDescriptor* service) { | |
| vars["service_name"] = service->name(); | |
| // 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(); | |
| p->Print(vars, "\n.setSchemaDescriptor(null)"); | |
| for (int i = 0; i < service->method_count(); ++i) { | |
| auto method = service->method(i); | |
| vars["method_method_name"] = MethodPropertiesGetterName(&*method); | |
| p->Print(vars, "\n.addMethod($method_method_name$())"); | |
| } | |
| 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(Printer* p, VARS& vars, | |
| const ServiceDescriptor* service) { | |
| 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) { | |
| auto method = service->method(i); | |
| vars["lower_method_name"] = LowerMethodName(&*method); | |
| vars["method_method_name"] = MethodPropertiesGetterName(&*method); | |
| vars["input_type"] = JavaClassName(vars, method->get_input_type_name()); | |
| vars["output_type"] = JavaClassName(vars, method->get_output_type_name()); | |
| vars["method_id_name"] = MethodIdFieldName(&*method); | |
| bool client_streaming = | |
| method->ClientStreaming() || method->BidiStreaming(); | |
| bool server_streaming = | |
| method->ServerStreaming() || method->BidiStreaming(); | |
| 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$(),\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(Printer* p, VARS& vars, | |
| const ServiceDescriptor* service, | |
| bool disable_version) { | |
| vars["service_name"] = service->name(); | |
| vars["service_class_name"] = ServiceClassName(service->name()); | |
| vars["grpc_version"] = ""; | |
| #ifdef GRPC_VERSION | |
| if (!disable_version) { | |
| vars["grpc_version"] = " (version " XSTR(GRPC_VERSION) ")"; | |
| } | |
| #else | |
| (void)disable_version; | |
| #endif | |
| // TODO(nmittler): Replace with WriteServiceDocComment once included by | |
| // protobuf distro. | |
| GrpcWriteServiceDocComment(p, vars, service); | |
| p->Print(vars, | |
| "@$Generated$(\n" | |
| " value = \"by gRPC proto compiler$grpc_version$\",\n" | |
| " comments = \"Source: $file_name$.fbs\")\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(p, vars, service); | |
| // TODO(nmittler): Replace with WriteDocComment once included by protobuf | |
| // distro. | |
| GrpcWriteDocComment( | |
| p, vars, | |
| " 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, vars, | |
| " 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, vars, | |
| " 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"); | |
| PrintStub(p, vars, service, ABSTRACT_CLASS); | |
| PrintStub(p, vars, service, ASYNC_CLIENT_IMPL); | |
| PrintStub(p, vars, service, BLOCKING_CLIENT_IMPL); | |
| PrintStub(p, vars, service, FUTURE_CLIENT_IMPL); | |
| PrintMethodHandlerClass(p, vars, service); | |
| PrintGetServiceDescriptorMethod(p, vars, service); | |
| p->Outdent(); | |
| p->Print("}\n"); | |
| } | |
| static void PrintStaticImports(Printer* p) { | |
| p->Print( | |
| "import java.nio.ByteBuffer;\n" | |
| "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"); | |
| } | |
| static void GenerateService(const grpc_generator::Service* service, | |
| grpc_generator::Printer* printer, VARS& vars, | |
| bool disable_version) { | |
| // All non-generated classes must be referred by fully qualified names to | |
| // avoid collision with generated classes. | |
| 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"; | |
| PrintStaticImports(printer); | |
| PrintService(printer, vars, service, disable_version); | |
| } | |
| } // namespace | |
| grpc::string GenerateServiceSource( | |
| grpc_generator::File* file, const grpc_generator::Service* service, | |
| grpc_java_generator::Parameters* parameters) { | |
| grpc::string out; | |
| auto printer = file->CreatePrinter(&out); | |
| VARS vars; | |
| vars["flatc_version"] = grpc::string( | |
| FLATBUFFERS_STRING(FLATBUFFERS_VERSION_MAJOR) "." FLATBUFFERS_STRING( | |
| FLATBUFFERS_VERSION_MINOR) "." FLATBUFFERS_STRING(FLATBUFFERS_VERSION_REVISION)); | |
| vars["file_name"] = file->filename(); | |
| if (!parameters->package_name.empty()) { | |
| vars["Package"] = parameters->package_name; // ServiceJavaPackage(service); | |
| } | |
| GenerateImports(file, &*printer, vars); | |
| GenerateService(service, &*printer, vars, false); | |
| return out; | |
| } | |
| } // namespace grpc_java_generator |