Home | History | Annotate | Download | only in compiler
      1 /*
      2  *
      3  * Copyright 2015 gRPC authors.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  *
     17  */
     18 
     19 #include <cctype>
     20 #include <map>
     21 #include <sstream>
     22 #include <vector>
     23 
     24 #include "src/compiler/config.h"
     25 #include "src/compiler/csharp_generator.h"
     26 #include "src/compiler/csharp_generator_helpers.h"
     27 
     28 using google::protobuf::compiler::csharp::GetClassName;
     29 using google::protobuf::compiler::csharp::GetFileNamespace;
     30 using google::protobuf::compiler::csharp::GetReflectionClassName;
     31 using grpc::protobuf::Descriptor;
     32 using grpc::protobuf::FileDescriptor;
     33 using grpc::protobuf::MethodDescriptor;
     34 using grpc::protobuf::ServiceDescriptor;
     35 using grpc::protobuf::io::Printer;
     36 using grpc::protobuf::io::StringOutputStream;
     37 using grpc_generator::GetMethodType;
     38 using grpc_generator::METHODTYPE_BIDI_STREAMING;
     39 using grpc_generator::METHODTYPE_CLIENT_STREAMING;
     40 using grpc_generator::METHODTYPE_NO_STREAMING;
     41 using grpc_generator::METHODTYPE_SERVER_STREAMING;
     42 using grpc_generator::MethodType;
     43 using grpc_generator::StringReplace;
     44 using std::map;
     45 using std::vector;
     46 
     47 namespace grpc_csharp_generator {
     48 namespace {
     49 
     50 // This function is a massaged version of
     51 // https://github.com/google/protobuf/blob/master/src/google/protobuf/compiler/csharp/csharp_doc_comment.cc
     52 // Currently, we cannot easily reuse the functionality as
     53 // google/protobuf/compiler/csharp/csharp_doc_comment.h is not a public header.
     54 // TODO(jtattermusch): reuse the functionality from google/protobuf.
     55 bool GenerateDocCommentBodyImpl(grpc::protobuf::io::Printer* printer,
     56                                 grpc::protobuf::SourceLocation location) {
     57   grpc::string comments = location.leading_comments.empty()
     58                               ? location.trailing_comments
     59                               : location.leading_comments;
     60   if (comments.empty()) {
     61     return false;
     62   }
     63   // XML escaping... no need for apostrophes etc as the whole text is going to
     64   // be a child
     65   // node of a summary element, not part of an attribute.
     66   comments = grpc_generator::StringReplace(comments, "&", "&amp;", true);
     67   comments = grpc_generator::StringReplace(comments, "<", "&lt;", true);
     68 
     69   std::vector<grpc::string> lines;
     70   grpc_generator::Split(comments, '\n', &lines);
     71   // TODO: We really should work out which part to put in the summary and which
     72   // to put in the remarks...
     73   // but that needs to be part of a bigger effort to understand the markdown
     74   // better anyway.
     75   printer->Print("/// <summary>\n");
     76   bool last_was_empty = false;
     77   // We squash multiple blank lines down to one, and remove any trailing blank
     78   // lines. We need
     79   // to preserve the blank lines themselves, as this is relevant in the
     80   // markdown.
     81   // Note that we can't remove leading or trailing whitespace as *that's*
     82   // relevant in markdown too.
     83   // (We don't skip "just whitespace" lines, either.)
     84   for (std::vector<grpc::string>::iterator it = lines.begin();
     85        it != lines.end(); ++it) {
     86     grpc::string line = *it;
     87     if (line.empty()) {
     88       last_was_empty = true;
     89     } else {
     90       if (last_was_empty) {
     91         printer->Print("///\n");
     92       }
     93       last_was_empty = false;
     94       printer->Print("///$line$\n", "line", *it);
     95     }
     96   }
     97   printer->Print("/// </summary>\n");
     98   return true;
     99 }
    100 
    101 template <typename DescriptorType>
    102 bool GenerateDocCommentBody(grpc::protobuf::io::Printer* printer,
    103                             const DescriptorType* descriptor) {
    104   grpc::protobuf::SourceLocation location;
    105   if (!descriptor->GetSourceLocation(&location)) {
    106     return false;
    107   }
    108   return GenerateDocCommentBodyImpl(printer, location);
    109 }
    110 
    111 void GenerateDocCommentServerMethod(grpc::protobuf::io::Printer* printer,
    112                                     const MethodDescriptor* method) {
    113   if (GenerateDocCommentBody(printer, method)) {
    114     if (method->client_streaming()) {
    115       printer->Print(
    116           "/// <param name=\"requestStream\">Used for reading requests from "
    117           "the client.</param>\n");
    118     } else {
    119       printer->Print(
    120           "/// <param name=\"request\">The request received from the "
    121           "client.</param>\n");
    122     }
    123     if (method->server_streaming()) {
    124       printer->Print(
    125           "/// <param name=\"responseStream\">Used for sending responses back "
    126           "to the client.</param>\n");
    127     }
    128     printer->Print(
    129         "/// <param name=\"context\">The context of the server-side call "
    130         "handler being invoked.</param>\n");
    131     if (method->server_streaming()) {
    132       printer->Print(
    133           "/// <returns>A task indicating completion of the "
    134           "handler.</returns>\n");
    135     } else {
    136       printer->Print(
    137           "/// <returns>The response to send back to the client (wrapped by a "
    138           "task).</returns>\n");
    139     }
    140   }
    141 }
    142 
    143 void GenerateDocCommentClientMethod(grpc::protobuf::io::Printer* printer,
    144                                     const MethodDescriptor* method,
    145                                     bool is_sync, bool use_call_options) {
    146   if (GenerateDocCommentBody(printer, method)) {
    147     if (!method->client_streaming()) {
    148       printer->Print(
    149           "/// <param name=\"request\">The request to send to the "
    150           "server.</param>\n");
    151     }
    152     if (!use_call_options) {
    153       printer->Print(
    154           "/// <param name=\"headers\">The initial metadata to send with the "
    155           "call. This parameter is optional.</param>\n");
    156       printer->Print(
    157           "/// <param name=\"deadline\">An optional deadline for the call. The "
    158           "call will be cancelled if deadline is hit.</param>\n");
    159       printer->Print(
    160           "/// <param name=\"cancellationToken\">An optional token for "
    161           "canceling the call.</param>\n");
    162     } else {
    163       printer->Print(
    164           "/// <param name=\"options\">The options for the call.</param>\n");
    165     }
    166     if (is_sync) {
    167       printer->Print(
    168           "/// <returns>The response received from the server.</returns>\n");
    169     } else {
    170       printer->Print("/// <returns>The call object.</returns>\n");
    171     }
    172   }
    173 }
    174 
    175 std::string GetServiceClassName(const ServiceDescriptor* service) {
    176   return service->name();
    177 }
    178 
    179 std::string GetClientClassName(const ServiceDescriptor* service) {
    180   return service->name() + "Client";
    181 }
    182 
    183 std::string GetServerClassName(const ServiceDescriptor* service) {
    184   return service->name() + "Base";
    185 }
    186 
    187 std::string GetCSharpMethodType(MethodType method_type) {
    188   switch (method_type) {
    189     case METHODTYPE_NO_STREAMING:
    190       return "grpc::MethodType.Unary";
    191     case METHODTYPE_CLIENT_STREAMING:
    192       return "grpc::MethodType.ClientStreaming";
    193     case METHODTYPE_SERVER_STREAMING:
    194       return "grpc::MethodType.ServerStreaming";
    195     case METHODTYPE_BIDI_STREAMING:
    196       return "grpc::MethodType.DuplexStreaming";
    197   }
    198   GOOGLE_LOG(FATAL) << "Can't get here.";
    199   return "";
    200 }
    201 
    202 std::string GetServiceNameFieldName() { return "__ServiceName"; }
    203 
    204 std::string GetMarshallerFieldName(const Descriptor* message) {
    205   return "__Marshaller_" +
    206          grpc_generator::StringReplace(message->full_name(), ".", "_", true);
    207 }
    208 
    209 std::string GetMethodFieldName(const MethodDescriptor* method) {
    210   return "__Method_" + method->name();
    211 }
    212 
    213 std::string GetMethodRequestParamMaybe(const MethodDescriptor* method,
    214                                        bool invocation_param = false) {
    215   if (method->client_streaming()) {
    216     return "";
    217   }
    218   if (invocation_param) {
    219     return "request, ";
    220   }
    221   return GetClassName(method->input_type()) + " request, ";
    222 }
    223 
    224 std::string GetAccessLevel(bool internal_access) {
    225   return internal_access ? "internal" : "public";
    226 }
    227 
    228 std::string GetMethodReturnTypeClient(const MethodDescriptor* method) {
    229   switch (GetMethodType(method)) {
    230     case METHODTYPE_NO_STREAMING:
    231       return "grpc::AsyncUnaryCall<" + GetClassName(method->output_type()) +
    232              ">";
    233     case METHODTYPE_CLIENT_STREAMING:
    234       return "grpc::AsyncClientStreamingCall<" +
    235              GetClassName(method->input_type()) + ", " +
    236              GetClassName(method->output_type()) + ">";
    237     case METHODTYPE_SERVER_STREAMING:
    238       return "grpc::AsyncServerStreamingCall<" +
    239              GetClassName(method->output_type()) + ">";
    240     case METHODTYPE_BIDI_STREAMING:
    241       return "grpc::AsyncDuplexStreamingCall<" +
    242              GetClassName(method->input_type()) + ", " +
    243              GetClassName(method->output_type()) + ">";
    244   }
    245   GOOGLE_LOG(FATAL) << "Can't get here.";
    246   return "";
    247 }
    248 
    249 std::string GetMethodRequestParamServer(const MethodDescriptor* method) {
    250   switch (GetMethodType(method)) {
    251     case METHODTYPE_NO_STREAMING:
    252     case METHODTYPE_SERVER_STREAMING:
    253       return GetClassName(method->input_type()) + " request";
    254     case METHODTYPE_CLIENT_STREAMING:
    255     case METHODTYPE_BIDI_STREAMING:
    256       return "grpc::IAsyncStreamReader<" + GetClassName(method->input_type()) +
    257              "> requestStream";
    258   }
    259   GOOGLE_LOG(FATAL) << "Can't get here.";
    260   return "";
    261 }
    262 
    263 std::string GetMethodReturnTypeServer(const MethodDescriptor* method) {
    264   switch (GetMethodType(method)) {
    265     case METHODTYPE_NO_STREAMING:
    266     case METHODTYPE_CLIENT_STREAMING:
    267       return "global::System.Threading.Tasks.Task<" +
    268              GetClassName(method->output_type()) + ">";
    269     case METHODTYPE_SERVER_STREAMING:
    270     case METHODTYPE_BIDI_STREAMING:
    271       return "global::System.Threading.Tasks.Task";
    272   }
    273   GOOGLE_LOG(FATAL) << "Can't get here.";
    274   return "";
    275 }
    276 
    277 std::string GetMethodResponseStreamMaybe(const MethodDescriptor* method) {
    278   switch (GetMethodType(method)) {
    279     case METHODTYPE_NO_STREAMING:
    280     case METHODTYPE_CLIENT_STREAMING:
    281       return "";
    282     case METHODTYPE_SERVER_STREAMING:
    283     case METHODTYPE_BIDI_STREAMING:
    284       return ", grpc::IServerStreamWriter<" +
    285              GetClassName(method->output_type()) + "> responseStream";
    286   }
    287   GOOGLE_LOG(FATAL) << "Can't get here.";
    288   return "";
    289 }
    290 
    291 // Gets vector of all messages used as input or output types.
    292 std::vector<const Descriptor*> GetUsedMessages(
    293     const ServiceDescriptor* service) {
    294   std::set<const Descriptor*> descriptor_set;
    295   std::vector<const Descriptor*>
    296       result;  // vector is to maintain stable ordering
    297   for (int i = 0; i < service->method_count(); i++) {
    298     const MethodDescriptor* method = service->method(i);
    299     if (descriptor_set.find(method->input_type()) == descriptor_set.end()) {
    300       descriptor_set.insert(method->input_type());
    301       result.push_back(method->input_type());
    302     }
    303     if (descriptor_set.find(method->output_type()) == descriptor_set.end()) {
    304       descriptor_set.insert(method->output_type());
    305       result.push_back(method->output_type());
    306     }
    307   }
    308   return result;
    309 }
    310 
    311 void GenerateMarshallerFields(Printer* out, const ServiceDescriptor* service) {
    312   std::vector<const Descriptor*> used_messages = GetUsedMessages(service);
    313   for (size_t i = 0; i < used_messages.size(); i++) {
    314     const Descriptor* message = used_messages[i];
    315     out->Print(
    316         "static readonly grpc::Marshaller<$type$> $fieldname$ = "
    317         "grpc::Marshallers.Create((arg) => "
    318         "global::Google.Protobuf.MessageExtensions.ToByteArray(arg), "
    319         "$type$.Parser.ParseFrom);\n",
    320         "fieldname", GetMarshallerFieldName(message), "type",
    321         GetClassName(message));
    322   }
    323   out->Print("\n");
    324 }
    325 
    326 void GenerateStaticMethodField(Printer* out, const MethodDescriptor* method) {
    327   out->Print(
    328       "static readonly grpc::Method<$request$, $response$> $fieldname$ = new "
    329       "grpc::Method<$request$, $response$>(\n",
    330       "fieldname", GetMethodFieldName(method), "request",
    331       GetClassName(method->input_type()), "response",
    332       GetClassName(method->output_type()));
    333   out->Indent();
    334   out->Indent();
    335   out->Print("$methodtype$,\n", "methodtype",
    336              GetCSharpMethodType(GetMethodType(method)));
    337   out->Print("$servicenamefield$,\n", "servicenamefield",
    338              GetServiceNameFieldName());
    339   out->Print("\"$methodname$\",\n", "methodname", method->name());
    340   out->Print("$requestmarshaller$,\n", "requestmarshaller",
    341              GetMarshallerFieldName(method->input_type()));
    342   out->Print("$responsemarshaller$);\n", "responsemarshaller",
    343              GetMarshallerFieldName(method->output_type()));
    344   out->Print("\n");
    345   out->Outdent();
    346   out->Outdent();
    347 }
    348 
    349 void GenerateServiceDescriptorProperty(Printer* out,
    350                                        const ServiceDescriptor* service) {
    351   std::ostringstream index;
    352   index << service->index();
    353   out->Print("/// <summary>Service descriptor</summary>\n");
    354   out->Print(
    355       "public static global::Google.Protobuf.Reflection.ServiceDescriptor "
    356       "Descriptor\n");
    357   out->Print("{\n");
    358   out->Print("  get { return $umbrella$.Descriptor.Services[$index$]; }\n",
    359              "umbrella", GetReflectionClassName(service->file()), "index",
    360              index.str());
    361   out->Print("}\n");
    362   out->Print("\n");
    363 }
    364 
    365 void GenerateServerClass(Printer* out, const ServiceDescriptor* service) {
    366   out->Print(
    367       "/// <summary>Base class for server-side implementations of "
    368       "$servicename$</summary>\n",
    369       "servicename", GetServiceClassName(service));
    370   out->Print("public abstract partial class $name$\n", "name",
    371              GetServerClassName(service));
    372   out->Print("{\n");
    373   out->Indent();
    374   for (int i = 0; i < service->method_count(); i++) {
    375     const MethodDescriptor* method = service->method(i);
    376     GenerateDocCommentServerMethod(out, method);
    377     out->Print(
    378         "public virtual $returntype$ "
    379         "$methodname$($request$$response_stream_maybe$, "
    380         "grpc::ServerCallContext context)\n",
    381         "methodname", method->name(), "returntype",
    382         GetMethodReturnTypeServer(method), "request",
    383         GetMethodRequestParamServer(method), "response_stream_maybe",
    384         GetMethodResponseStreamMaybe(method));
    385     out->Print("{\n");
    386     out->Indent();
    387     out->Print(
    388         "throw new grpc::RpcException("
    389         "new grpc::Status(grpc::StatusCode.Unimplemented, \"\"));\n");
    390     out->Outdent();
    391     out->Print("}\n\n");
    392   }
    393   out->Outdent();
    394   out->Print("}\n");
    395   out->Print("\n");
    396 }
    397 
    398 void GenerateClientStub(Printer* out, const ServiceDescriptor* service) {
    399   out->Print("/// <summary>Client for $servicename$</summary>\n", "servicename",
    400              GetServiceClassName(service));
    401   out->Print("public partial class $name$ : grpc::ClientBase<$name$>\n", "name",
    402              GetClientClassName(service));
    403   out->Print("{\n");
    404   out->Indent();
    405 
    406   // constructors
    407   out->Print(
    408       "/// <summary>Creates a new client for $servicename$</summary>\n"
    409       "/// <param name=\"channel\">The channel to use to make remote "
    410       "calls.</param>\n",
    411       "servicename", GetServiceClassName(service));
    412   out->Print("public $name$(grpc::Channel channel) : base(channel)\n", "name",
    413              GetClientClassName(service));
    414   out->Print("{\n");
    415   out->Print("}\n");
    416   out->Print(
    417       "/// <summary>Creates a new client for $servicename$ that uses a custom "
    418       "<c>CallInvoker</c>.</summary>\n"
    419       "/// <param name=\"callInvoker\">The callInvoker to use to make remote "
    420       "calls.</param>\n",
    421       "servicename", GetServiceClassName(service));
    422   out->Print(
    423       "public $name$(grpc::CallInvoker callInvoker) : base(callInvoker)\n",
    424       "name", GetClientClassName(service));
    425   out->Print("{\n");
    426   out->Print("}\n");
    427   out->Print(
    428       "/// <summary>Protected parameterless constructor to allow creation"
    429       " of test doubles.</summary>\n");
    430   out->Print("protected $name$() : base()\n", "name",
    431              GetClientClassName(service));
    432   out->Print("{\n");
    433   out->Print("}\n");
    434   out->Print(
    435       "/// <summary>Protected constructor to allow creation of configured "
    436       "clients.</summary>\n"
    437       "/// <param name=\"configuration\">The client configuration.</param>\n");
    438   out->Print(
    439       "protected $name$(ClientBaseConfiguration configuration)"
    440       " : base(configuration)\n",
    441       "name", GetClientClassName(service));
    442   out->Print("{\n");
    443   out->Print("}\n\n");
    444 
    445   for (int i = 0; i < service->method_count(); i++) {
    446     const MethodDescriptor* method = service->method(i);
    447     MethodType method_type = GetMethodType(method);
    448 
    449     if (method_type == METHODTYPE_NO_STREAMING) {
    450       // unary calls have an extra synchronous stub method
    451       GenerateDocCommentClientMethod(out, method, true, false);
    452       out->Print(
    453           "public virtual $response$ $methodname$($request$ request, "
    454           "grpc::Metadata "
    455           "headers = null, global::System.DateTime? deadline = null, "
    456           "global::System.Threading.CancellationToken "
    457           "cancellationToken = "
    458           "default(global::System.Threading.CancellationToken))\n",
    459           "methodname", method->name(), "request",
    460           GetClassName(method->input_type()), "response",
    461           GetClassName(method->output_type()));
    462       out->Print("{\n");
    463       out->Indent();
    464       out->Print(
    465           "return $methodname$(request, new grpc::CallOptions(headers, "
    466           "deadline, "
    467           "cancellationToken));\n",
    468           "methodname", method->name());
    469       out->Outdent();
    470       out->Print("}\n");
    471 
    472       // overload taking CallOptions as a param
    473       GenerateDocCommentClientMethod(out, method, true, true);
    474       out->Print(
    475           "public virtual $response$ $methodname$($request$ request, "
    476           "grpc::CallOptions options)\n",
    477           "methodname", method->name(), "request",
    478           GetClassName(method->input_type()), "response",
    479           GetClassName(method->output_type()));
    480       out->Print("{\n");
    481       out->Indent();
    482       out->Print(
    483           "return CallInvoker.BlockingUnaryCall($methodfield$, null, options, "
    484           "request);\n",
    485           "methodfield", GetMethodFieldName(method));
    486       out->Outdent();
    487       out->Print("}\n");
    488     }
    489 
    490     std::string method_name = method->name();
    491     if (method_type == METHODTYPE_NO_STREAMING) {
    492       method_name += "Async";  // prevent name clash with synchronous method.
    493     }
    494     GenerateDocCommentClientMethod(out, method, false, false);
    495     out->Print(
    496         "public virtual $returntype$ "
    497         "$methodname$($request_maybe$grpc::Metadata "
    498         "headers = null, global::System.DateTime? deadline = null, "
    499         "global::System.Threading.CancellationToken "
    500         "cancellationToken = "
    501         "default(global::System.Threading.CancellationToken))\n",
    502         "methodname", method_name, "request_maybe",
    503         GetMethodRequestParamMaybe(method), "returntype",
    504         GetMethodReturnTypeClient(method));
    505     out->Print("{\n");
    506     out->Indent();
    507 
    508     out->Print(
    509         "return $methodname$($request_maybe$new grpc::CallOptions(headers, "
    510         "deadline, "
    511         "cancellationToken));\n",
    512         "methodname", method_name, "request_maybe",
    513         GetMethodRequestParamMaybe(method, true));
    514     out->Outdent();
    515     out->Print("}\n");
    516 
    517     // overload taking CallOptions as a param
    518     GenerateDocCommentClientMethod(out, method, false, true);
    519     out->Print(
    520         "public virtual $returntype$ "
    521         "$methodname$($request_maybe$grpc::CallOptions "
    522         "options)\n",
    523         "methodname", method_name, "request_maybe",
    524         GetMethodRequestParamMaybe(method), "returntype",
    525         GetMethodReturnTypeClient(method));
    526     out->Print("{\n");
    527     out->Indent();
    528     switch (GetMethodType(method)) {
    529       case METHODTYPE_NO_STREAMING:
    530         out->Print(
    531             "return CallInvoker.AsyncUnaryCall($methodfield$, null, options, "
    532             "request);\n",
    533             "methodfield", GetMethodFieldName(method));
    534         break;
    535       case METHODTYPE_CLIENT_STREAMING:
    536         out->Print(
    537             "return CallInvoker.AsyncClientStreamingCall($methodfield$, null, "
    538             "options);\n",
    539             "methodfield", GetMethodFieldName(method));
    540         break;
    541       case METHODTYPE_SERVER_STREAMING:
    542         out->Print(
    543             "return CallInvoker.AsyncServerStreamingCall($methodfield$, null, "
    544             "options, request);\n",
    545             "methodfield", GetMethodFieldName(method));
    546         break;
    547       case METHODTYPE_BIDI_STREAMING:
    548         out->Print(
    549             "return CallInvoker.AsyncDuplexStreamingCall($methodfield$, null, "
    550             "options);\n",
    551             "methodfield", GetMethodFieldName(method));
    552         break;
    553       default:
    554         GOOGLE_LOG(FATAL) << "Can't get here.";
    555     }
    556     out->Outdent();
    557     out->Print("}\n");
    558   }
    559 
    560   // override NewInstance method
    561   out->Print(
    562       "/// <summary>Creates a new instance of client from given "
    563       "<c>ClientBaseConfiguration</c>.</summary>\n");
    564   out->Print(
    565       "protected override $name$ NewInstance(ClientBaseConfiguration "
    566       "configuration)\n",
    567       "name", GetClientClassName(service));
    568   out->Print("{\n");
    569   out->Indent();
    570   out->Print("return new $name$(configuration);\n", "name",
    571              GetClientClassName(service));
    572   out->Outdent();
    573   out->Print("}\n");
    574 
    575   out->Outdent();
    576   out->Print("}\n");
    577   out->Print("\n");
    578 }
    579 
    580 void GenerateBindServiceMethod(Printer* out, const ServiceDescriptor* service) {
    581   out->Print(
    582       "/// <summary>Creates service definition that can be registered with a "
    583       "server</summary>\n");
    584   out->Print(
    585       "/// <param name=\"serviceImpl\">An object implementing the server-side"
    586       " handling logic.</param>\n");
    587   out->Print(
    588       "public static grpc::ServerServiceDefinition BindService($implclass$ "
    589       "serviceImpl)\n",
    590       "implclass", GetServerClassName(service));
    591   out->Print("{\n");
    592   out->Indent();
    593 
    594   out->Print("return grpc::ServerServiceDefinition.CreateBuilder()");
    595   out->Indent();
    596   out->Indent();
    597   for (int i = 0; i < service->method_count(); i++) {
    598     const MethodDescriptor* method = service->method(i);
    599     out->Print("\n.AddMethod($methodfield$, serviceImpl.$methodname$)",
    600                "methodfield", GetMethodFieldName(method), "methodname",
    601                method->name());
    602   }
    603   out->Print(".Build();\n");
    604   out->Outdent();
    605   out->Outdent();
    606 
    607   out->Outdent();
    608   out->Print("}\n");
    609   out->Print("\n");
    610 }
    611 
    612 void GenerateService(Printer* out, const ServiceDescriptor* service,
    613                      bool generate_client, bool generate_server,
    614                      bool internal_access) {
    615   GenerateDocCommentBody(out, service);
    616   out->Print("$access_level$ static partial class $classname$\n",
    617              "access_level", GetAccessLevel(internal_access), "classname",
    618              GetServiceClassName(service));
    619   out->Print("{\n");
    620   out->Indent();
    621   out->Print("static readonly string $servicenamefield$ = \"$servicename$\";\n",
    622              "servicenamefield", GetServiceNameFieldName(), "servicename",
    623              service->full_name());
    624   out->Print("\n");
    625 
    626   GenerateMarshallerFields(out, service);
    627   for (int i = 0; i < service->method_count(); i++) {
    628     GenerateStaticMethodField(out, service->method(i));
    629   }
    630   GenerateServiceDescriptorProperty(out, service);
    631 
    632   if (generate_server) {
    633     GenerateServerClass(out, service);
    634   }
    635   if (generate_client) {
    636     GenerateClientStub(out, service);
    637   }
    638   if (generate_server) {
    639     GenerateBindServiceMethod(out, service);
    640   }
    641 
    642   out->Outdent();
    643   out->Print("}\n");
    644 }
    645 
    646 }  // anonymous namespace
    647 
    648 grpc::string GetServices(const FileDescriptor* file, bool generate_client,
    649                          bool generate_server, bool internal_access) {
    650   grpc::string output;
    651   {
    652     // Scope the output stream so it closes and finalizes output to the string.
    653 
    654     StringOutputStream output_stream(&output);
    655     Printer out(&output_stream, '$');
    656 
    657     // Don't write out any output if there no services, to avoid empty service
    658     // files being generated for proto files that don't declare any.
    659     if (file->service_count() == 0) {
    660       return output;
    661     }
    662 
    663     // Write out a file header.
    664     out.Print("// <auto-generated>\n");
    665     out.Print(
    666         "//     Generated by the protocol buffer compiler.  DO NOT EDIT!\n");
    667     out.Print("//     source: $filename$\n", "filename", file->name());
    668     out.Print("// </auto-generated>\n");
    669 
    670     // use C++ style as there are no file-level XML comments in .NET
    671     grpc::string leading_comments = GetCsharpComments(file, true);
    672     if (!leading_comments.empty()) {
    673       out.Print("// Original file comments:\n");
    674       out.PrintRaw(leading_comments.c_str());
    675     }
    676 
    677     out.Print("#pragma warning disable 0414, 1591\n");
    678 
    679     out.Print("#region Designer generated code\n");
    680     out.Print("\n");
    681     out.Print("using grpc = global::Grpc.Core;\n");
    682     out.Print("\n");
    683 
    684     grpc::string file_namespace = GetFileNamespace(file);
    685     if (file_namespace != "") {
    686       out.Print("namespace $namespace$ {\n", "namespace", file_namespace);
    687       out.Indent();
    688     }
    689     for (int i = 0; i < file->service_count(); i++) {
    690       GenerateService(&out, file->service(i), generate_client, generate_server,
    691                       internal_access);
    692     }
    693     if (file_namespace != "") {
    694       out.Outdent();
    695       out.Print("}\n");
    696     }
    697     out.Print("#endregion\n");
    698   }
    699   return output;
    700 }
    701 
    702 }  // namespace grpc_csharp_generator
    703