Feng Xiao | e841bac | 2015-12-11 17:09:20 -0800 | [diff] [blame] | 1 | // Protocol Buffers - Google's data interchange format |
| 2 | // Copyright 2008 Google Inc. All rights reserved. |
| 3 | // https://developers.google.com/protocol-buffers/ |
| 4 | // |
| 5 | // Redistribution and use in source and binary forms, with or without |
| 6 | // modification, are permitted provided that the following conditions are |
| 7 | // met: |
| 8 | // |
| 9 | // * Redistributions of source code must retain the above copyright |
| 10 | // notice, this list of conditions and the following disclaimer. |
| 11 | // * Redistributions in binary form must reproduce the above |
| 12 | // copyright notice, this list of conditions and the following disclaimer |
| 13 | // in the documentation and/or other materials provided with the |
| 14 | // distribution. |
| 15 | // * Neither the name of Google Inc. nor the names of its |
| 16 | // contributors may be used to endorse or promote products derived from |
| 17 | // this software without specific prior written permission. |
| 18 | // |
| 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 30 | |
| 31 | #include <google/protobuf/compiler/js/js_generator.h> |
| 32 | |
| 33 | #include <assert.h> |
| 34 | #include <algorithm> |
| 35 | #include <limits> |
| 36 | #include <map> |
| 37 | #include <memory> |
| 38 | #ifndef _SHARED_PTR_H |
| 39 | #include <google/protobuf/stubs/shared_ptr.h> |
| 40 | #endif |
| 41 | #include <string> |
| 42 | #include <utility> |
| 43 | #include <vector> |
| 44 | |
| 45 | #include <google/protobuf/stubs/logging.h> |
| 46 | #include <google/protobuf/stubs/common.h> |
| 47 | #include <google/protobuf/stubs/stringprintf.h> |
| 48 | #include <google/protobuf/io/printer.h> |
| 49 | #include <google/protobuf/io/zero_copy_stream.h> |
| 50 | #include <google/protobuf/descriptor.pb.h> |
| 51 | #include <google/protobuf/descriptor.h> |
| 52 | #include <google/protobuf/stubs/strutil.h> |
| 53 | |
| 54 | namespace google { |
| 55 | namespace protobuf { |
| 56 | namespace compiler { |
| 57 | namespace js { |
| 58 | |
| 59 | // Sorted list of JavaScript keywords. These cannot be used as names. If they |
| 60 | // appear, we prefix them with "pb_". |
| 61 | const char* kKeyword[] = { |
| 62 | "abstract", |
| 63 | "boolean", |
| 64 | "break", |
| 65 | "byte", |
| 66 | "case", |
| 67 | "catch", |
| 68 | "char", |
| 69 | "class", |
| 70 | "const", |
| 71 | "continue", |
| 72 | "debugger", |
| 73 | "default", |
| 74 | "delete", |
| 75 | "do", |
| 76 | "double", |
| 77 | "else", |
| 78 | "enum", |
| 79 | "export", |
| 80 | "extends", |
| 81 | "false", |
| 82 | "final", |
| 83 | "finally", |
| 84 | "float", |
| 85 | "for", |
| 86 | "function", |
| 87 | "goto", |
| 88 | "if", |
| 89 | "implements", |
| 90 | "import", |
| 91 | "in", |
| 92 | "instanceof", |
| 93 | "int", |
| 94 | "interface", |
| 95 | "long", |
| 96 | "native", |
| 97 | "new", |
| 98 | "null", |
| 99 | "package", |
| 100 | "private", |
| 101 | "protected", |
| 102 | "public", |
| 103 | "return", |
| 104 | "short", |
| 105 | "static", |
| 106 | "super", |
| 107 | "switch", |
| 108 | "synchronized", |
| 109 | "this", |
| 110 | "throw", |
| 111 | "throws", |
| 112 | "transient", |
| 113 | "try", |
| 114 | "typeof", |
| 115 | "var", |
| 116 | "void", |
| 117 | "volatile", |
| 118 | "while", |
| 119 | "with", |
| 120 | }; |
| 121 | |
| 122 | static const int kNumKeyword = sizeof(kKeyword) / sizeof(char*); |
| 123 | |
| 124 | namespace { |
| 125 | |
| 126 | bool IsReserved(const string& ident) { |
| 127 | for (int i = 0; i < kNumKeyword; i++) { |
| 128 | if (ident == kKeyword[i]) { |
| 129 | return true; |
| 130 | } |
| 131 | } |
| 132 | return false; |
| 133 | } |
| 134 | |
| 135 | // Returns a copy of |filename| with any trailing ".protodevel" or ".proto |
| 136 | // suffix stripped. |
| 137 | string StripProto(const string& filename) { |
| 138 | const char* suffix = HasSuffixString(filename, ".protodevel") |
| 139 | ? ".protodevel" : ".proto"; |
| 140 | return StripSuffixString(filename, suffix); |
| 141 | } |
| 142 | |
| 143 | // Returns the fully normalized JavaScript path for the given |
| 144 | // file descriptor's package. |
| 145 | string GetPath(const GeneratorOptions& options, |
| 146 | const FileDescriptor* file) { |
| 147 | if (!options.namespace_prefix.empty()) { |
| 148 | return options.namespace_prefix; |
| 149 | } else if (!file->package().empty()) { |
| 150 | return "proto." + file->package(); |
| 151 | } else { |
| 152 | return "proto"; |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | // Forward declare, so that GetPrefix can call this method, |
| 157 | // which in turn, calls GetPrefix. |
| 158 | string GetPath(const GeneratorOptions& options, |
| 159 | const Descriptor* descriptor); |
| 160 | |
| 161 | // Returns the path prefix for a message or enumeration that |
| 162 | // lives under the given file and containing type. |
| 163 | string GetPrefix(const GeneratorOptions& options, |
| 164 | const FileDescriptor* file_descriptor, |
| 165 | const Descriptor* containing_type) { |
| 166 | string prefix = ""; |
| 167 | |
| 168 | if (containing_type == NULL) { |
| 169 | prefix = GetPath(options, file_descriptor); |
| 170 | } else { |
| 171 | prefix = GetPath(options, containing_type); |
| 172 | } |
| 173 | |
| 174 | if (!prefix.empty()) { |
| 175 | prefix += "."; |
| 176 | } |
| 177 | |
| 178 | return prefix; |
| 179 | } |
| 180 | |
| 181 | |
| 182 | // Returns the fully normalized JavaScript path for the given |
| 183 | // message descriptor. |
| 184 | string GetPath(const GeneratorOptions& options, |
| 185 | const Descriptor* descriptor) { |
| 186 | return GetPrefix( |
| 187 | options, descriptor->file(), |
| 188 | descriptor->containing_type()) + descriptor->name(); |
| 189 | } |
| 190 | |
| 191 | |
| 192 | // Returns the fully normalized JavaScript path for the given |
| 193 | // field's containing message descriptor. |
| 194 | string GetPath(const GeneratorOptions& options, |
| 195 | const FieldDescriptor* descriptor) { |
| 196 | return GetPath(options, descriptor->containing_type()); |
| 197 | } |
| 198 | |
| 199 | // Returns the fully normalized JavaScript path for the given |
| 200 | // enumeration descriptor. |
| 201 | string GetPath(const GeneratorOptions& options, |
| 202 | const EnumDescriptor* enum_descriptor) { |
| 203 | return GetPrefix( |
| 204 | options, enum_descriptor->file(), |
| 205 | enum_descriptor->containing_type()) + enum_descriptor->name(); |
| 206 | } |
| 207 | |
| 208 | |
| 209 | // Returns the fully normalized JavaScript path for the given |
| 210 | // enumeration value descriptor. |
| 211 | string GetPath(const GeneratorOptions& options, |
| 212 | const EnumValueDescriptor* value_descriptor) { |
| 213 | return GetPath( |
| 214 | options, |
| 215 | value_descriptor->type()) + "." + value_descriptor->name(); |
| 216 | } |
| 217 | |
| 218 | // - Object field name: LOWER_UNDERSCORE -> LOWER_CAMEL, except for group fields |
| 219 | // (UPPER_CAMEL -> LOWER_CAMEL), with "List" (or "Map") appended if appropriate, |
| 220 | // and with reserved words triggering a "pb_" prefix. |
| 221 | // - Getters/setters: LOWER_UNDERSCORE -> UPPER_CAMEL, except for group fields |
| 222 | // (use the name directly), then append "List" if appropriate, then append "$" |
| 223 | // if resulting name is equal to a reserved word. |
| 224 | // - Enums: just uppercase. |
| 225 | |
| 226 | // Locale-independent version of ToLower that deals only with ASCII A-Z. |
| 227 | char ToLowerASCII(char c) { |
| 228 | if (c >= 'A' && c <= 'Z') { |
| 229 | return (c - 'A') + 'a'; |
| 230 | } else { |
| 231 | return c; |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | vector<string> ParseLowerUnderscore(const string& input) { |
| 236 | vector<string> words; |
| 237 | string running = ""; |
| 238 | for (int i = 0; i < input.size(); i++) { |
| 239 | if (input[i] == '_') { |
| 240 | if (!running.empty()) { |
| 241 | words.push_back(running); |
| 242 | running.clear(); |
| 243 | } |
| 244 | } else { |
| 245 | running += ToLowerASCII(input[i]); |
| 246 | } |
| 247 | } |
| 248 | if (!running.empty()) { |
| 249 | words.push_back(running); |
| 250 | } |
| 251 | return words; |
| 252 | } |
| 253 | |
| 254 | vector<string> ParseUpperCamel(const string& input) { |
| 255 | vector<string> words; |
| 256 | string running = ""; |
| 257 | for (int i = 0; i < input.size(); i++) { |
| 258 | if (input[i] >= 'A' && input[i] <= 'Z' && !running.empty()) { |
| 259 | words.push_back(running); |
| 260 | running.clear(); |
| 261 | } |
| 262 | running += ToLowerASCII(input[i]); |
| 263 | } |
| 264 | if (!running.empty()) { |
| 265 | words.push_back(running); |
| 266 | } |
| 267 | return words; |
| 268 | } |
| 269 | |
| 270 | string ToLowerCamel(const vector<string>& words) { |
| 271 | string result; |
| 272 | for (int i = 0; i < words.size(); i++) { |
| 273 | string word = words[i]; |
| 274 | if (i == 0 && (word[0] >= 'A' && word[0] <= 'Z')) { |
| 275 | word[0] = (word[0] - 'A') + 'a'; |
| 276 | } else if (i != 0 && (word[0] >= 'a' && word[0] <= 'z')) { |
| 277 | word[0] = (word[0] - 'a') + 'A'; |
| 278 | } |
| 279 | result += word; |
| 280 | } |
| 281 | return result; |
| 282 | } |
| 283 | |
| 284 | string ToUpperCamel(const vector<string>& words) { |
| 285 | string result; |
| 286 | for (int i = 0; i < words.size(); i++) { |
| 287 | string word = words[i]; |
| 288 | if (word[0] >= 'a' && word[0] <= 'z') { |
| 289 | word[0] = (word[0] - 'a') + 'A'; |
| 290 | } |
| 291 | result += word; |
| 292 | } |
| 293 | return result; |
| 294 | } |
| 295 | |
| 296 | // Based on code from descriptor.cc (Thanks Kenton!) |
| 297 | // Uppercases the entire string, turning ValueName into |
| 298 | // VALUENAME. |
| 299 | string ToEnumCase(const string& input) { |
| 300 | string result; |
| 301 | result.reserve(input.size()); |
| 302 | |
| 303 | for (int i = 0; i < input.size(); i++) { |
| 304 | if ('a' <= input[i] && input[i] <= 'z') { |
| 305 | result.push_back(input[i] - 'a' + 'A'); |
| 306 | } else { |
| 307 | result.push_back(input[i]); |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | return result; |
| 312 | } |
| 313 | |
| 314 | string ToFileName(const string& input) { |
| 315 | string result; |
| 316 | result.reserve(input.size()); |
| 317 | |
| 318 | for (int i = 0; i < input.size(); i++) { |
| 319 | if ('A' <= input[i] && input[i] <= 'Z') { |
| 320 | result.push_back(input[i] - 'A' + 'a'); |
| 321 | } else { |
| 322 | result.push_back(input[i]); |
| 323 | } |
| 324 | } |
| 325 | |
| 326 | return result; |
| 327 | } |
| 328 | |
| 329 | // Returns the message/response ID, if set. |
| 330 | string GetMessageId(const Descriptor* desc) { |
| 331 | return string(); |
| 332 | } |
| 333 | |
| 334 | |
| 335 | // Used inside Google only -- do not remove. |
| 336 | bool IsResponse(const Descriptor* desc) { return false; } |
| 337 | bool IgnoreField(const FieldDescriptor* field) { return false; } |
| 338 | |
| 339 | |
| 340 | // Does JSPB ignore this entire oneof? True only if all fields are ignored. |
| 341 | bool IgnoreOneof(const OneofDescriptor* oneof) { |
| 342 | for (int i = 0; i < oneof->field_count(); i++) { |
| 343 | if (!IgnoreField(oneof->field(i))) { |
| 344 | return false; |
| 345 | } |
| 346 | } |
| 347 | return true; |
| 348 | } |
| 349 | |
| 350 | string JSIdent(const FieldDescriptor* field, |
| 351 | bool is_upper_camel, |
| 352 | bool is_map) { |
| 353 | string result; |
| 354 | if (field->type() == FieldDescriptor::TYPE_GROUP) { |
| 355 | result = is_upper_camel ? |
| 356 | ToUpperCamel(ParseUpperCamel(field->message_type()->name())) : |
| 357 | ToLowerCamel(ParseUpperCamel(field->message_type()->name())); |
| 358 | } else { |
| 359 | result = is_upper_camel ? |
| 360 | ToUpperCamel(ParseLowerUnderscore(field->name())) : |
| 361 | ToLowerCamel(ParseLowerUnderscore(field->name())); |
| 362 | } |
| 363 | if (is_map) { |
| 364 | result += "Map"; |
| 365 | } else if (field->is_repeated()) { |
| 366 | result += "List"; |
| 367 | } |
| 368 | return result; |
| 369 | } |
| 370 | |
| 371 | string JSObjectFieldName(const FieldDescriptor* field) { |
| 372 | string name = JSIdent( |
| 373 | field, |
| 374 | /* is_upper_camel = */ false, |
| 375 | /* is_map = */ false); |
| 376 | if (IsReserved(name)) { |
| 377 | name = "pb_" + name; |
| 378 | } |
| 379 | return name; |
| 380 | } |
| 381 | |
| 382 | // Returns the field name as a capitalized portion of a getter/setter method |
| 383 | // name, e.g. MyField for .getMyField(). |
| 384 | string JSGetterName(const FieldDescriptor* field) { |
| 385 | string name = JSIdent(field, |
| 386 | /* is_upper_camel = */ true, |
| 387 | /* is_map = */ false); |
| 388 | if (name == "Extension" || name == "JsPbMessageId") { |
| 389 | // Avoid conflicts with base-class names. |
| 390 | name += "$"; |
| 391 | } |
| 392 | return name; |
| 393 | } |
| 394 | |
| 395 | string JSMapGetterName(const FieldDescriptor* field) { |
| 396 | return JSIdent(field, |
| 397 | /* is_upper_camel = */ true, |
| 398 | /* is_map = */ true); |
| 399 | } |
| 400 | |
| 401 | |
| 402 | |
| 403 | string JSOneofName(const OneofDescriptor* oneof) { |
| 404 | return ToUpperCamel(ParseLowerUnderscore(oneof->name())); |
| 405 | } |
| 406 | |
| 407 | // Returns the index corresponding to this field in the JSPB array (underlying |
| 408 | // data storage array). |
| 409 | string JSFieldIndex(const FieldDescriptor* field) { |
| 410 | // Determine whether this field is a member of a group. Group fields are a bit |
| 411 | // wonky: their "containing type" is a message type created just for the |
| 412 | // group, and that type's parent type has a field with the group-message type |
| 413 | // as its message type and TYPE_GROUP as its field type. For such fields, the |
| 414 | // index we use is relative to the field number of the group submessage field. |
| 415 | // For all other fields, we just use the field number. |
| 416 | const Descriptor* containing_type = field->containing_type(); |
| 417 | const Descriptor* parent_type = containing_type->containing_type(); |
| 418 | if (parent_type != NULL) { |
| 419 | for (int i = 0; i < parent_type->field_count(); i++) { |
| 420 | if (parent_type->field(i)->type() == FieldDescriptor::TYPE_GROUP && |
| 421 | parent_type->field(i)->message_type() == containing_type) { |
| 422 | return SimpleItoa(field->number() - parent_type->field(i)->number()); |
| 423 | } |
| 424 | } |
| 425 | } |
| 426 | return SimpleItoa(field->number()); |
| 427 | } |
| 428 | |
| 429 | string JSOneofIndex(const OneofDescriptor* oneof) { |
| 430 | int index = -1; |
| 431 | for (int i = 0; i < oneof->containing_type()->oneof_decl_count(); i++) { |
| 432 | const OneofDescriptor* o = oneof->containing_type()->oneof_decl(i); |
| 433 | // If at least one field in this oneof is not JSPB-ignored, count the oneof. |
| 434 | for (int j = 0; j < o->field_count(); j++) { |
| 435 | const FieldDescriptor* f = o->field(j); |
| 436 | if (!IgnoreField(f)) { |
| 437 | index++; |
| 438 | break; // inner loop |
| 439 | } |
| 440 | } |
| 441 | if (o == oneof) { |
| 442 | break; |
| 443 | } |
| 444 | } |
| 445 | return SimpleItoa(index); |
| 446 | } |
| 447 | |
| 448 | // Decodes a codepoint in \x0000 -- \xFFFF. Since JS strings are UTF-16, we only |
| 449 | // need to handle the BMP (16-bit range) here. |
| 450 | uint16_t DecodeUTF8Codepoint(uint8_t* bytes, size_t* length) { |
| 451 | if (*length == 0) { |
| 452 | return 0; |
| 453 | } |
| 454 | size_t expected = 0; |
| 455 | if ((*bytes & 0x80) == 0) { |
| 456 | expected = 1; |
| 457 | } else if ((*bytes & 0xe0) == 0xc0) { |
| 458 | expected = 2; |
| 459 | } else if ((*bytes & 0xf0) == 0xe0) { |
| 460 | expected = 3; |
| 461 | } else { |
| 462 | // Too long -- don't accept. |
| 463 | *length = 0; |
| 464 | return 0; |
| 465 | } |
| 466 | |
| 467 | if (*length < expected) { |
| 468 | // Not enough bytes -- don't accept. |
| 469 | *length = 0; |
| 470 | return 0; |
| 471 | } |
| 472 | |
| 473 | *length = expected; |
| 474 | switch (expected) { |
| 475 | case 1: return bytes[0]; |
| 476 | case 2: return ((bytes[0] & 0x1F) << 6) | |
| 477 | ((bytes[1] & 0x3F) << 0); |
| 478 | case 3: return ((bytes[0] & 0x0F) << 12) | |
| 479 | ((bytes[1] & 0x3F) << 6) | |
| 480 | ((bytes[2] & 0x3F) << 0); |
| 481 | default: return 0; |
| 482 | } |
| 483 | } |
| 484 | |
| 485 | // Escapes the contents of a string to be included within double-quotes ("") in |
| 486 | // JavaScript. |is_utf8| determines whether the input data (in a C++ string of |
| 487 | // chars) is UTF-8 encoded (in which case codepoints become JavaScript string |
| 488 | // characters, escaped with 16-bit hex escapes where necessary) or raw binary |
| 489 | // (in which case bytes become JavaScript string characters 0 -- 255). |
| 490 | string EscapeJSString(const string& in, bool is_utf8) { |
| 491 | string result; |
| 492 | size_t decoded = 0; |
| 493 | for (size_t i = 0; i < in.size(); i += decoded) { |
| 494 | uint16_t codepoint = 0; |
| 495 | if (is_utf8) { |
| 496 | // Decode the next UTF-8 codepoint. |
| 497 | size_t have_bytes = in.size() - i; |
| 498 | uint8_t bytes[3] = { |
| 499 | static_cast<uint8_t>(in[i]), |
| 500 | static_cast<uint8_t>(((i + 1) < in.size()) ? in[i + 1] : 0), |
| 501 | static_cast<uint8_t>(((i + 2) < in.size()) ? in[i + 2] : 0), |
| 502 | }; |
| 503 | codepoint = DecodeUTF8Codepoint(bytes, &have_bytes); |
| 504 | if (have_bytes == 0) { |
| 505 | break; |
| 506 | } |
| 507 | decoded = have_bytes; |
| 508 | } else { |
| 509 | codepoint = static_cast<uint16_t>(static_cast<uint8_t>(in[i])); |
| 510 | decoded = 1; |
| 511 | } |
| 512 | |
| 513 | // Next byte -- used for minimal octal escapes below. |
| 514 | char next_byte = (i + decoded) < in.size() ? |
| 515 | in[i + decoded] : 0; |
| 516 | bool pad_octal = (next_byte >= '0' && next_byte <= '7'); |
| 517 | |
| 518 | switch (codepoint) { |
| 519 | case '\0': result += pad_octal ? "\\000" : "\\0"; break; |
| 520 | case '\b': result += "\\\b"; break; |
| 521 | case '\t': result += "\\\t"; break; |
| 522 | case '\n': result += "\\\n"; break; |
| 523 | case '\r': result += "\\\r"; break; |
| 524 | case '\f': result += "\\\f"; break; |
| 525 | case '\\': result += "\\\\"; break; |
| 526 | case '"': result += pad_octal ? "\\042" : "\\42"; break; |
| 527 | case '&': result += pad_octal ? "\\046" : "\\46"; break; |
| 528 | case '\'': result += pad_octal ? "\\047" : "\\47"; break; |
| 529 | case '<': result += pad_octal ? "\\074" : "\\74"; break; |
| 530 | case '=': result += pad_octal ? "\\075" : "\\75"; break; |
| 531 | case '>': result += pad_octal ? "\\076" : "\\76"; break; |
| 532 | default: |
| 533 | // All other non-ASCII codepoints are escaped. |
| 534 | // Original codegen uses hex for >= 0x100 and octal for others. |
| 535 | if (codepoint >= 0x20 && codepoint <= 0x7e) { |
| 536 | result += static_cast<char>(codepoint); |
| 537 | } else { |
| 538 | if (codepoint >= 0x100) { |
| 539 | result += StringPrintf("\\u%04x", codepoint); |
| 540 | } else { |
| 541 | if (pad_octal || codepoint >= 0100) { |
| 542 | result += "\\"; |
| 543 | result += ('0' + ((codepoint >> 6) & 07)); |
| 544 | result += ('0' + ((codepoint >> 3) & 07)); |
| 545 | result += ('0' + ((codepoint >> 0) & 07)); |
| 546 | } else if (codepoint >= 010) { |
| 547 | result += "\\"; |
| 548 | result += ('0' + ((codepoint >> 3) & 07)); |
| 549 | result += ('0' + ((codepoint >> 0) & 07)); |
| 550 | } else { |
| 551 | result += "\\"; |
| 552 | result += ('0' + ((codepoint >> 0) & 07)); |
| 553 | } |
| 554 | } |
| 555 | } |
| 556 | break; |
| 557 | } |
| 558 | } |
| 559 | return result; |
| 560 | } |
| 561 | |
| 562 | string EscapeBase64(const string& in) { |
| 563 | static const char* kAlphabet = |
| 564 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
| 565 | string result; |
| 566 | |
| 567 | for (size_t i = 0; i < in.size(); i += 3) { |
| 568 | int value = (in[i] << 16) | |
| 569 | (((i + 1) < in.size()) ? (in[i + 1] << 8) : 0) | |
| 570 | (((i + 2) < in.size()) ? (in[i + 2] << 0) : 0); |
| 571 | result += kAlphabet[(value >> 18) & 0x3f]; |
| 572 | result += kAlphabet[(value >> 12) & 0x3f]; |
| 573 | if ((i + 1) < in.size()) { |
| 574 | result += kAlphabet[(value >> 6) & 0x3f]; |
| 575 | } else { |
| 576 | result += '='; |
| 577 | } |
| 578 | if ((i + 2) < in.size()) { |
| 579 | result += kAlphabet[(value >> 0) & 0x3f]; |
| 580 | } else { |
| 581 | result += '='; |
| 582 | } |
| 583 | } |
| 584 | |
| 585 | return result; |
| 586 | } |
| 587 | |
| 588 | // Post-process the result of SimpleFtoa/SimpleDtoa to *exactly* match the |
| 589 | // original codegen's formatting (which is just .toString() on java.lang.Double |
| 590 | // or java.lang.Float). |
| 591 | string PostProcessFloat(string result) { |
| 592 | // If inf, -inf or nan, replace with +Infinity, -Infinity or NaN. |
| 593 | if (result == "inf") { |
| 594 | return "Infinity"; |
| 595 | } else if (result == "-inf") { |
| 596 | return "-Infinity"; |
| 597 | } else if (result == "nan") { |
| 598 | return "NaN"; |
| 599 | } |
| 600 | |
| 601 | // If scientific notation (e.g., "1e10"), (i) capitalize the "e", (ii) |
| 602 | // ensure that the mantissa (portion prior to the "e") has at least one |
| 603 | // fractional digit (after the decimal point), and (iii) strip any unnecessary |
| 604 | // leading zeroes and/or '+' signs from the exponent. |
| 605 | string::size_type exp_pos = result.find('e'); |
| 606 | if (exp_pos != string::npos) { |
| 607 | string mantissa = result.substr(0, exp_pos); |
| 608 | string exponent = result.substr(exp_pos + 1); |
| 609 | |
| 610 | // Add ".0" to mantissa if no fractional part exists. |
| 611 | if (mantissa.find('.') == string::npos) { |
| 612 | mantissa += ".0"; |
| 613 | } |
| 614 | |
| 615 | // Strip the sign off the exponent and store as |exp_neg|. |
| 616 | bool exp_neg = false; |
| 617 | if (!exponent.empty() && exponent[0] == '+') { |
| 618 | exponent = exponent.substr(1); |
| 619 | } else if (!exponent.empty() && exponent[0] == '-') { |
| 620 | exp_neg = true; |
| 621 | exponent = exponent.substr(1); |
| 622 | } |
| 623 | |
| 624 | // Strip any leading zeroes off the exponent. |
| 625 | while (exponent.size() > 1 && exponent[0] == '0') { |
| 626 | exponent = exponent.substr(1); |
| 627 | } |
| 628 | |
| 629 | return mantissa + "E" + string(exp_neg ? "-" : "") + exponent; |
| 630 | } |
| 631 | |
| 632 | // Otherwise, this is an ordinary decimal number. Append ".0" if result has no |
| 633 | // decimal/fractional part in order to match output of original codegen. |
| 634 | if (result.find('.') == string::npos) { |
| 635 | result += ".0"; |
| 636 | } |
| 637 | |
| 638 | return result; |
| 639 | } |
| 640 | |
| 641 | string FloatToString(float value) { |
| 642 | string result = SimpleFtoa(value); |
| 643 | return PostProcessFloat(result); |
| 644 | } |
| 645 | |
| 646 | string DoubleToString(double value) { |
| 647 | string result = SimpleDtoa(value); |
| 648 | return PostProcessFloat(result); |
| 649 | } |
| 650 | |
| 651 | string MaybeNumberString(const FieldDescriptor* field, const string& orig) { |
| 652 | return orig; |
| 653 | } |
| 654 | |
| 655 | string JSFieldDefault(const FieldDescriptor* field) { |
| 656 | assert(field->has_default_value()); |
| 657 | switch (field->cpp_type()) { |
| 658 | case FieldDescriptor::CPPTYPE_INT32: |
| 659 | return MaybeNumberString( |
| 660 | field, SimpleItoa(field->default_value_int32())); |
| 661 | case FieldDescriptor::CPPTYPE_UINT32: |
| 662 | // The original codegen is in Java, and Java protobufs store unsigned |
| 663 | // integer values as signed integer values. In order to exactly match the |
| 664 | // output, we need to reinterpret as base-2 signed. Ugh. |
| 665 | return MaybeNumberString( |
| 666 | field, SimpleItoa(static_cast<int32>(field->default_value_uint32()))); |
| 667 | case FieldDescriptor::CPPTYPE_INT64: |
| 668 | return MaybeNumberString( |
| 669 | field, SimpleItoa(field->default_value_int64())); |
| 670 | case FieldDescriptor::CPPTYPE_UINT64: |
| 671 | // See above note for uint32 -- reinterpreting as signed. |
| 672 | return MaybeNumberString( |
| 673 | field, SimpleItoa(static_cast<int64>(field->default_value_uint64()))); |
| 674 | case FieldDescriptor::CPPTYPE_ENUM: |
| 675 | return SimpleItoa(field->default_value_enum()->number()); |
| 676 | case FieldDescriptor::CPPTYPE_BOOL: |
| 677 | return field->default_value_bool() ? "true" : "false"; |
| 678 | case FieldDescriptor::CPPTYPE_FLOAT: |
| 679 | return FloatToString(field->default_value_float()); |
| 680 | case FieldDescriptor::CPPTYPE_DOUBLE: |
| 681 | return DoubleToString(field->default_value_double()); |
| 682 | case FieldDescriptor::CPPTYPE_STRING: |
| 683 | if (field->type() == FieldDescriptor::TYPE_STRING) { |
| 684 | return "\"" + EscapeJSString(field->default_value_string(), true) + |
| 685 | "\""; |
| 686 | } else { |
| 687 | return "\"" + EscapeBase64(field->default_value_string()) + |
| 688 | "\""; |
| 689 | } |
| 690 | case FieldDescriptor::CPPTYPE_MESSAGE: |
| 691 | return "null"; |
| 692 | } |
| 693 | } |
| 694 | |
| 695 | string ProtoTypeName(const GeneratorOptions& options, |
| 696 | const FieldDescriptor* field) { |
| 697 | switch (field->type()) { |
| 698 | case FieldDescriptor::TYPE_BOOL: |
| 699 | return "bool"; |
| 700 | case FieldDescriptor::TYPE_INT32: |
| 701 | return "int32"; |
| 702 | case FieldDescriptor::TYPE_UINT32: |
| 703 | return "uint32"; |
| 704 | case FieldDescriptor::TYPE_SINT32: |
| 705 | return "sint32"; |
| 706 | case FieldDescriptor::TYPE_FIXED32: |
| 707 | return "fixed32"; |
| 708 | case FieldDescriptor::TYPE_SFIXED32: |
| 709 | return "sfixed32"; |
| 710 | case FieldDescriptor::TYPE_INT64: |
| 711 | return "int64"; |
| 712 | case FieldDescriptor::TYPE_UINT64: |
| 713 | return "uint64"; |
| 714 | case FieldDescriptor::TYPE_SINT64: |
| 715 | return "sint64"; |
| 716 | case FieldDescriptor::TYPE_FIXED64: |
| 717 | return "fixed64"; |
| 718 | case FieldDescriptor::TYPE_SFIXED64: |
| 719 | return "sfixed64"; |
| 720 | case FieldDescriptor::TYPE_FLOAT: |
| 721 | return "float"; |
| 722 | case FieldDescriptor::TYPE_DOUBLE: |
| 723 | return "double"; |
| 724 | case FieldDescriptor::TYPE_STRING: |
| 725 | return "string"; |
| 726 | case FieldDescriptor::TYPE_BYTES: |
| 727 | return "bytes"; |
| 728 | case FieldDescriptor::TYPE_GROUP: |
| 729 | return GetPath(options, field->message_type()); |
| 730 | case FieldDescriptor::TYPE_ENUM: |
| 731 | return GetPath(options, field->enum_type()); |
| 732 | case FieldDescriptor::TYPE_MESSAGE: |
| 733 | return GetPath(options, field->message_type()); |
| 734 | default: |
| 735 | return ""; |
| 736 | } |
| 737 | } |
| 738 | |
| 739 | string JSIntegerTypeName(const FieldDescriptor* field) { |
| 740 | return "number"; |
| 741 | } |
| 742 | |
| 743 | string JSTypeName(const GeneratorOptions& options, |
| 744 | const FieldDescriptor* field) { |
| 745 | switch (field->cpp_type()) { |
| 746 | case FieldDescriptor::CPPTYPE_BOOL: |
| 747 | return "boolean"; |
| 748 | case FieldDescriptor::CPPTYPE_INT32: |
| 749 | return JSIntegerTypeName(field); |
| 750 | case FieldDescriptor::CPPTYPE_INT64: |
| 751 | return JSIntegerTypeName(field); |
| 752 | case FieldDescriptor::CPPTYPE_UINT32: |
| 753 | return JSIntegerTypeName(field); |
| 754 | case FieldDescriptor::CPPTYPE_UINT64: |
| 755 | return JSIntegerTypeName(field); |
| 756 | case FieldDescriptor::CPPTYPE_FLOAT: |
| 757 | return "number"; |
| 758 | case FieldDescriptor::CPPTYPE_DOUBLE: |
| 759 | return "number"; |
| 760 | case FieldDescriptor::CPPTYPE_STRING: |
| 761 | return "string"; |
| 762 | case FieldDescriptor::CPPTYPE_ENUM: |
| 763 | return GetPath(options, field->enum_type()); |
| 764 | case FieldDescriptor::CPPTYPE_MESSAGE: |
| 765 | return GetPath(options, field->message_type()); |
| 766 | default: |
| 767 | return ""; |
| 768 | } |
| 769 | } |
| 770 | |
| 771 | bool HasFieldPresence(const FieldDescriptor* field); |
| 772 | |
| 773 | string JSFieldTypeAnnotation(const GeneratorOptions& options, |
| 774 | const FieldDescriptor* field, |
| 775 | bool force_optional, |
| 776 | bool force_present, |
| 777 | bool singular_if_not_packed, |
| 778 | bool always_singular) { |
| 779 | bool is_primitive = |
| 780 | (field->cpp_type() != FieldDescriptor::CPPTYPE_ENUM && |
| 781 | field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE); |
| 782 | |
| 783 | string jstype = JSTypeName(options, field); |
| 784 | |
| 785 | if (field->is_repeated() && |
| 786 | !always_singular && |
| 787 | (field->is_packed() || !singular_if_not_packed)) { |
| 788 | if (!is_primitive) { |
| 789 | jstype = "!" + jstype; |
| 790 | } |
| 791 | jstype = "Array.<" + jstype + ">"; |
| 792 | if (!force_optional) { |
| 793 | jstype = "!" + jstype; |
| 794 | } |
| 795 | } |
| 796 | |
| 797 | if (field->is_optional() && is_primitive && |
| 798 | (!field->has_default_value() || force_optional) && !force_present) { |
| 799 | jstype += "?"; |
| 800 | } else if (field->is_required() && !is_primitive && !force_optional) { |
| 801 | jstype = "!" + jstype; |
| 802 | } |
| 803 | |
| 804 | if (force_optional && HasFieldPresence(field)) { |
| 805 | jstype += "|undefined"; |
| 806 | } |
| 807 | if (force_present && jstype[0] != '!' && !is_primitive) { |
| 808 | jstype = "!" + jstype; |
| 809 | } |
| 810 | |
| 811 | return jstype; |
| 812 | } |
| 813 | |
| 814 | string JSBinaryReaderMethodType(const FieldDescriptor* field) { |
| 815 | string name = field->type_name(); |
| 816 | if (name[0] >= 'a' && name[0] <= 'z') { |
| 817 | name[0] = (name[0] - 'a') + 'A'; |
| 818 | } |
| 819 | |
| 820 | return name; |
| 821 | } |
| 822 | |
| 823 | string JSBinaryReadWriteMethodName(const FieldDescriptor* field, |
| 824 | bool is_writer) { |
| 825 | string name = JSBinaryReaderMethodType(field); |
| 826 | if (is_writer && field->type() == FieldDescriptor::TYPE_BYTES) { |
| 827 | // Override for `bytes` fields: treat string as raw bytes, not base64. |
| 828 | name = "BytesRawString"; |
| 829 | } |
| 830 | if (field->is_packed()) { |
| 831 | name = "Packed" + name; |
| 832 | } else if (is_writer && field->is_repeated()) { |
| 833 | name = "Repeated" + name; |
| 834 | } |
| 835 | return name; |
| 836 | } |
| 837 | |
| 838 | string JSBinaryReaderMethodName(const FieldDescriptor* field) { |
| 839 | return "read" + JSBinaryReadWriteMethodName(field, /* is_writer = */ false); |
| 840 | } |
| 841 | |
| 842 | string JSBinaryWriterMethodName(const FieldDescriptor* field) { |
| 843 | return "write" + JSBinaryReadWriteMethodName(field, /* is_writer = */ true); |
| 844 | } |
| 845 | |
| 846 | string JSReturnClause(const FieldDescriptor* desc) { |
| 847 | return ""; |
| 848 | } |
| 849 | |
| 850 | string JSReturnDoc(const GeneratorOptions& options, |
| 851 | const FieldDescriptor* desc) { |
| 852 | return ""; |
| 853 | } |
| 854 | |
| 855 | bool HasRepeatedFields(const Descriptor* desc) { |
| 856 | for (int i = 0; i < desc->field_count(); i++) { |
| 857 | if (desc->field(i)->is_repeated()) { |
| 858 | return true; |
| 859 | } |
| 860 | } |
| 861 | return false; |
| 862 | } |
| 863 | |
| 864 | static const char* kRepeatedFieldArrayName = ".repeatedFields_"; |
| 865 | |
| 866 | string RepeatedFieldsArrayName(const GeneratorOptions& options, |
| 867 | const Descriptor* desc) { |
| 868 | return HasRepeatedFields(desc) ? |
| 869 | (GetPath(options, desc) + kRepeatedFieldArrayName) : "null"; |
| 870 | } |
| 871 | |
| 872 | bool HasOneofFields(const Descriptor* desc) { |
| 873 | for (int i = 0; i < desc->field_count(); i++) { |
| 874 | if (desc->field(i)->containing_oneof()) { |
| 875 | return true; |
| 876 | } |
| 877 | } |
| 878 | return false; |
| 879 | } |
| 880 | |
| 881 | static const char* kOneofGroupArrayName = ".oneofGroups_"; |
| 882 | |
| 883 | string OneofFieldsArrayName(const GeneratorOptions& options, |
| 884 | const Descriptor* desc) { |
| 885 | return HasOneofFields(desc) ? |
| 886 | (GetPath(options, desc) + kOneofGroupArrayName) : "null"; |
| 887 | } |
| 888 | |
| 889 | string RepeatedFieldNumberList(const Descriptor* desc) { |
| 890 | std::vector<string> numbers; |
| 891 | for (int i = 0; i < desc->field_count(); i++) { |
| 892 | if (desc->field(i)->is_repeated()) { |
| 893 | numbers.push_back(JSFieldIndex(desc->field(i))); |
| 894 | } |
| 895 | } |
| 896 | return "[" + Join(numbers, ",") + "]"; |
| 897 | } |
| 898 | |
| 899 | string OneofGroupList(const Descriptor* desc) { |
| 900 | // List of arrays (one per oneof), each of which is a list of field indices |
| 901 | std::vector<string> oneof_entries; |
| 902 | for (int i = 0; i < desc->oneof_decl_count(); i++) { |
| 903 | const OneofDescriptor* oneof = desc->oneof_decl(i); |
| 904 | if (IgnoreOneof(oneof)) { |
| 905 | continue; |
| 906 | } |
| 907 | |
| 908 | std::vector<string> oneof_fields; |
| 909 | for (int j = 0; j < oneof->field_count(); j++) { |
| 910 | if (IgnoreField(oneof->field(j))) { |
| 911 | continue; |
| 912 | } |
| 913 | oneof_fields.push_back(JSFieldIndex(oneof->field(j))); |
| 914 | } |
| 915 | oneof_entries.push_back("[" + Join(oneof_fields, ",") + "]"); |
| 916 | } |
| 917 | return "[" + Join(oneof_entries, ",") + "]"; |
| 918 | } |
| 919 | |
| 920 | string JSOneofArray(const GeneratorOptions& options, |
| 921 | const FieldDescriptor* field) { |
| 922 | return OneofFieldsArrayName(options, field->containing_type()) + "[" + |
| 923 | JSOneofIndex(field->containing_oneof()) + "]"; |
| 924 | } |
| 925 | |
| 926 | string RelativeTypeName(const FieldDescriptor* field) { |
| 927 | assert(field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM || |
| 928 | field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE); |
| 929 | // For a field with an enum or message type, compute a name relative to the |
| 930 | // path name of the message type containing this field. |
| 931 | string package = field->file()->package(); |
| 932 | string containing_type = field->containing_type()->full_name() + "."; |
| 933 | string type = (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) ? |
| 934 | field->enum_type()->full_name() : field->message_type()->full_name(); |
| 935 | |
| 936 | // |prefix| is advanced as we find separators '.' past the common package |
| 937 | // prefix that yield common prefixes in the containing type's name and this |
| 938 | // type's name. |
| 939 | int prefix = 0; |
| 940 | for (int i = 0; i < type.size() && i < containing_type.size(); i++) { |
| 941 | if (type[i] != containing_type[i]) { |
| 942 | break; |
| 943 | } |
| 944 | if (type[i] == '.' && i >= package.size()) { |
| 945 | prefix = i + 1; |
| 946 | } |
| 947 | } |
| 948 | |
| 949 | return type.substr(prefix); |
| 950 | } |
| 951 | |
| 952 | string JSExtensionsObjectName(const GeneratorOptions& options, |
| 953 | const Descriptor* desc) { |
| 954 | if (desc->full_name() == "google.protobuf.bridge.MessageSet") { |
| 955 | return "jspb.Message.messageSetExtensions"; |
| 956 | } else { |
| 957 | return GetPath(options, desc) + ".extensions"; |
| 958 | } |
| 959 | } |
| 960 | |
| 961 | string FieldDefinition(const GeneratorOptions& options, |
| 962 | const FieldDescriptor* field) { |
| 963 | string qualifier = field->is_repeated() ? "repeated" : |
| 964 | (field->is_optional() ? "optional" : "required"); |
| 965 | string type, name; |
| 966 | if (field->type() == FieldDescriptor::TYPE_ENUM || |
| 967 | field->type() == FieldDescriptor::TYPE_MESSAGE) { |
| 968 | type = RelativeTypeName(field); |
| 969 | name = field->name(); |
| 970 | } else if (field->type() == FieldDescriptor::TYPE_GROUP) { |
| 971 | type = "group"; |
| 972 | name = field->message_type()->name(); |
| 973 | } else { |
| 974 | type = ProtoTypeName(options, field); |
| 975 | name = field->name(); |
| 976 | } |
| 977 | return StringPrintf("%s %s %s = %d;", |
| 978 | qualifier.c_str(), |
| 979 | type.c_str(), |
| 980 | name.c_str(), |
| 981 | field->number()); |
| 982 | } |
| 983 | |
| 984 | string FieldComments(const FieldDescriptor* field) { |
| 985 | string comments; |
| 986 | if (field->cpp_type() == FieldDescriptor::CPPTYPE_BOOL) { |
| 987 | comments += |
| 988 | " * Note that Boolean fields may be set to 0/1 when serialized from " |
| 989 | "a Java server.\n" |
| 990 | " * You should avoid comparisons like {@code val === true/false} in " |
| 991 | "those cases.\n"; |
| 992 | } |
| 993 | if (field->is_repeated()) { |
| 994 | comments += |
| 995 | " * If you change this array by adding, removing or replacing " |
| 996 | "elements, or if you\n" |
| 997 | " * replace the array itself, then you must call the setter to " |
| 998 | "update it.\n"; |
| 999 | } |
| 1000 | return comments; |
| 1001 | } |
| 1002 | |
| 1003 | bool ShouldGenerateExtension(const FieldDescriptor* field) { |
| 1004 | return |
| 1005 | field->is_extension() && |
| 1006 | !IgnoreField(field); |
| 1007 | } |
| 1008 | |
| 1009 | bool HasExtensions(const Descriptor* desc) { |
| 1010 | if (desc->extension_count() > 0) { |
| 1011 | return true; |
| 1012 | } |
| 1013 | for (int i = 0; i < desc->nested_type_count(); i++) { |
| 1014 | if (HasExtensions(desc->nested_type(i))) { |
| 1015 | return true; |
| 1016 | } |
| 1017 | } |
| 1018 | return false; |
| 1019 | } |
| 1020 | |
| 1021 | bool HasExtensions(const FileDescriptor* file) { |
| 1022 | for (int i = 0; i < file->extension_count(); i++) { |
| 1023 | if (ShouldGenerateExtension(file->extension(i))) { |
| 1024 | return true; |
| 1025 | } |
| 1026 | } |
| 1027 | for (int i = 0; i < file->message_type_count(); i++) { |
| 1028 | if (HasExtensions(file->message_type(i))) { |
| 1029 | return true; |
| 1030 | } |
| 1031 | } |
| 1032 | return false; |
| 1033 | } |
| 1034 | |
| 1035 | bool IsExtendable(const Descriptor* desc) { |
| 1036 | return desc->extension_range_count() > 0; |
| 1037 | } |
| 1038 | |
| 1039 | // Returns the max index in the underlying data storage array beyond which the |
| 1040 | // extension object is used. |
| 1041 | string GetPivot(const Descriptor* desc) { |
| 1042 | static const int kDefaultPivot = (1 << 29); // max field number (29 bits) |
| 1043 | |
| 1044 | // Find the max field number |
| 1045 | int max_field_number = 0; |
| 1046 | for (int i = 0; i < desc->field_count(); i++) { |
| 1047 | if (!IgnoreField(desc->field(i)) && |
| 1048 | desc->field(i)->number() > max_field_number) { |
| 1049 | max_field_number = desc->field(i)->number(); |
| 1050 | } |
| 1051 | } |
| 1052 | |
| 1053 | int pivot = -1; |
| 1054 | if (IsExtendable(desc)) { |
| 1055 | pivot = ((max_field_number + 1) < kDefaultPivot) ? |
| 1056 | (max_field_number + 1) : kDefaultPivot; |
| 1057 | } |
| 1058 | |
| 1059 | return SimpleItoa(pivot); |
| 1060 | } |
| 1061 | |
| 1062 | // Returns true for fields that represent "null" as distinct from the default |
| 1063 | // value. See https://go/proto3#heading=h.kozewqqcqhuz for more information. |
| 1064 | bool HasFieldPresence(const FieldDescriptor* field) { |
| 1065 | return |
| 1066 | (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) || |
| 1067 | (field->containing_oneof() != NULL) || |
| 1068 | (field->file()->syntax() != FileDescriptor::SYNTAX_PROTO3); |
| 1069 | } |
| 1070 | |
| 1071 | // For proto3 fields without presence, returns a string representing the default |
| 1072 | // value in JavaScript. See https://go/proto3#heading=h.kozewqqcqhuz for more |
| 1073 | // information. |
| 1074 | string Proto3PrimitiveFieldDefault(const FieldDescriptor* field) { |
| 1075 | switch (field->cpp_type()) { |
| 1076 | case FieldDescriptor::CPPTYPE_INT32: |
| 1077 | case FieldDescriptor::CPPTYPE_INT64: |
| 1078 | case FieldDescriptor::CPPTYPE_UINT32: |
| 1079 | case FieldDescriptor::CPPTYPE_UINT64: { |
| 1080 | return "0"; |
| 1081 | } |
| 1082 | |
| 1083 | case FieldDescriptor::CPPTYPE_ENUM: |
| 1084 | case FieldDescriptor::CPPTYPE_FLOAT: |
| 1085 | case FieldDescriptor::CPPTYPE_DOUBLE: |
| 1086 | return "0"; |
| 1087 | |
| 1088 | case FieldDescriptor::CPPTYPE_BOOL: |
| 1089 | return "false"; |
| 1090 | |
| 1091 | case FieldDescriptor::CPPTYPE_STRING: |
| 1092 | return "\"\""; |
| 1093 | |
| 1094 | default: |
| 1095 | // BYTES and MESSAGE are handled separately. |
| 1096 | assert(false); |
| 1097 | return ""; |
| 1098 | } |
| 1099 | } |
| 1100 | |
| 1101 | } // anonymous namespace |
| 1102 | |
| 1103 | void Generator::GenerateHeader(const GeneratorOptions& options, |
| 1104 | io::Printer* printer) const { |
| 1105 | printer->Print("/**\n" |
| 1106 | " * @fileoverview\n" |
| 1107 | " * @enhanceable\n" |
| 1108 | " * @public\n" |
| 1109 | " */\n" |
| 1110 | "// GENERATED CODE -- DO NOT EDIT!\n" |
| 1111 | "\n"); |
| 1112 | } |
| 1113 | |
| 1114 | void Generator::FindProvides(const GeneratorOptions& options, |
| 1115 | io::Printer* printer, |
| 1116 | const vector<const FileDescriptor*>& files, |
| 1117 | std::set<string>* provided) const { |
| 1118 | for (int i = 0; i < files.size(); i++) { |
| 1119 | for (int j = 0; j < files[i]->message_type_count(); j++) { |
| 1120 | FindProvidesForMessage(options, printer, files[i]->message_type(j), |
| 1121 | provided); |
| 1122 | } |
| 1123 | for (int j = 0; j < files[i]->enum_type_count(); j++) { |
| 1124 | FindProvidesForEnum(options, printer, files[i]->enum_type(j), |
| 1125 | provided); |
| 1126 | } |
| 1127 | } |
| 1128 | |
| 1129 | printer->Print("\n"); |
| 1130 | } |
| 1131 | |
| 1132 | void Generator::FindProvidesForMessage( |
| 1133 | const GeneratorOptions& options, |
| 1134 | io::Printer* printer, |
| 1135 | const Descriptor* desc, |
| 1136 | std::set<string>* provided) const { |
| 1137 | string name = GetPath(options, desc); |
| 1138 | provided->insert(name); |
| 1139 | |
| 1140 | for (int i = 0; i < desc->enum_type_count(); i++) { |
| 1141 | FindProvidesForEnum(options, printer, desc->enum_type(i), |
| 1142 | provided); |
| 1143 | } |
| 1144 | for (int i = 0; i < desc->nested_type_count(); i++) { |
| 1145 | FindProvidesForMessage(options, printer, desc->nested_type(i), |
| 1146 | provided); |
| 1147 | } |
| 1148 | } |
| 1149 | |
| 1150 | void Generator::FindProvidesForEnum(const GeneratorOptions& options, |
| 1151 | io::Printer* printer, |
| 1152 | const EnumDescriptor* enumdesc, |
| 1153 | std::set<string>* provided) const { |
| 1154 | string name = GetPath(options, enumdesc); |
| 1155 | provided->insert(name); |
| 1156 | } |
| 1157 | |
| 1158 | void Generator::FindProvidesForFields( |
| 1159 | const GeneratorOptions& options, |
| 1160 | io::Printer* printer, |
| 1161 | const vector<const FieldDescriptor*>& fields, |
| 1162 | std::set<string>* provided) const { |
| 1163 | for (int i = 0; i < fields.size(); i++) { |
| 1164 | const FieldDescriptor* field = fields[i]; |
| 1165 | |
| 1166 | if (IgnoreField(field)) { |
| 1167 | continue; |
| 1168 | } |
| 1169 | |
| 1170 | string name = |
| 1171 | GetPath(options, field->file()) + "." + JSObjectFieldName(field); |
| 1172 | provided->insert(name); |
| 1173 | } |
| 1174 | } |
| 1175 | |
| 1176 | void Generator::GenerateProvides(const GeneratorOptions& options, |
| 1177 | io::Printer* printer, |
| 1178 | std::set<string>* provided) const { |
| 1179 | for (std::set<string>::iterator it = provided->begin(); |
| 1180 | it != provided->end(); ++it) { |
| 1181 | printer->Print("goog.provide('$name$');\n", |
| 1182 | "name", *it); |
| 1183 | } |
| 1184 | } |
| 1185 | |
| 1186 | void Generator::GenerateRequires(const GeneratorOptions& options, |
| 1187 | io::Printer* printer, |
| 1188 | const Descriptor* desc, |
| 1189 | std::set<string>* provided) const { |
| 1190 | std::set<string> required; |
| 1191 | std::set<string> forwards; |
| 1192 | bool have_message = false; |
| 1193 | FindRequiresForMessage(options, desc, |
| 1194 | &required, &forwards, &have_message); |
| 1195 | |
| 1196 | GenerateRequiresImpl(options, printer, &required, &forwards, provided, |
| 1197 | /* require_jspb = */ have_message, |
| 1198 | /* require_extension = */ HasExtensions(desc)); |
| 1199 | } |
| 1200 | |
| 1201 | void Generator::GenerateRequires(const GeneratorOptions& options, |
| 1202 | io::Printer* printer, |
| 1203 | const vector<const FileDescriptor*>& files, |
| 1204 | std::set<string>* provided) const { |
| 1205 | std::set<string> required; |
| 1206 | std::set<string> forwards; |
| 1207 | bool have_extensions = false; |
| 1208 | bool have_message = false; |
| 1209 | |
| 1210 | for (int i = 0; i < files.size(); i++) { |
| 1211 | for (int j = 0; j < files[i]->message_type_count(); j++) { |
| 1212 | FindRequiresForMessage(options, |
| 1213 | files[i]->message_type(j), |
| 1214 | &required, &forwards, &have_message); |
| 1215 | } |
| 1216 | if (!have_extensions && HasExtensions(files[i])) { |
| 1217 | have_extensions = true; |
| 1218 | } |
| 1219 | |
| 1220 | for (int j = 0; j < files[i]->extension_count(); j++) { |
| 1221 | const FieldDescriptor* extension = files[i]->extension(j); |
| 1222 | if (IgnoreField(extension)) { |
| 1223 | continue; |
| 1224 | } |
| 1225 | if (extension->containing_type()->full_name() != |
| 1226 | "google.protobuf.bridge.MessageSet") { |
| 1227 | required.insert(GetPath(options, extension->containing_type())); |
| 1228 | } |
| 1229 | FindRequiresForField(options, extension, &required, &forwards); |
| 1230 | have_extensions = true; |
| 1231 | } |
| 1232 | } |
| 1233 | |
| 1234 | GenerateRequiresImpl(options, printer, &required, &forwards, provided, |
| 1235 | /* require_jspb = */ have_message, |
| 1236 | /* require_extension = */ have_extensions); |
| 1237 | } |
| 1238 | |
| 1239 | void Generator::GenerateRequires(const GeneratorOptions& options, |
| 1240 | io::Printer* printer, |
| 1241 | const vector<const FieldDescriptor*>& fields, |
| 1242 | std::set<string>* provided) const { |
| 1243 | std::set<string> required; |
| 1244 | std::set<string> forwards; |
| 1245 | for (int i = 0; i < fields.size(); i++) { |
| 1246 | const FieldDescriptor* field = fields[i]; |
| 1247 | if (IgnoreField(field)) { |
| 1248 | continue; |
| 1249 | } |
| 1250 | FindRequiresForExtension(options, field, &required, &forwards); |
| 1251 | } |
| 1252 | |
| 1253 | GenerateRequiresImpl(options, printer, &required, &forwards, provided, |
| 1254 | /* require_jspb = */ false, |
| 1255 | /* require_extension = */ fields.size() > 0); |
| 1256 | } |
| 1257 | |
| 1258 | void Generator::GenerateRequiresImpl(const GeneratorOptions& options, |
| 1259 | io::Printer* printer, |
| 1260 | std::set<string>* required, |
| 1261 | std::set<string>* forwards, |
| 1262 | std::set<string>* provided, |
| 1263 | bool require_jspb, |
| 1264 | bool require_extension) const { |
| 1265 | if (require_jspb) { |
| 1266 | printer->Print( |
| 1267 | "goog.require('jspb.Message');\n"); |
| 1268 | if (options.binary) { |
| 1269 | printer->Print( |
| 1270 | "goog.require('jspb.BinaryReader');\n" |
| 1271 | "goog.require('jspb.BinaryWriter');\n"); |
| 1272 | } |
| 1273 | } |
| 1274 | if (require_extension) { |
| 1275 | printer->Print( |
| 1276 | "goog.require('jspb.ExtensionFieldInfo');\n"); |
| 1277 | } |
| 1278 | |
| 1279 | std::set<string>::iterator it; |
| 1280 | for (it = required->begin(); it != required->end(); ++it) { |
| 1281 | if (provided->find(*it) != provided->end()) { |
| 1282 | continue; |
| 1283 | } |
| 1284 | printer->Print("goog.require('$name$');\n", |
| 1285 | "name", *it); |
| 1286 | } |
| 1287 | |
| 1288 | printer->Print("\n"); |
| 1289 | |
| 1290 | for (it = forwards->begin(); it != forwards->end(); ++it) { |
| 1291 | if (provided->find(*it) != provided->end()) { |
| 1292 | continue; |
| 1293 | } |
| 1294 | printer->Print("goog.forwardDeclare('$name$');\n", |
| 1295 | "name", *it); |
| 1296 | } |
| 1297 | } |
| 1298 | |
| 1299 | bool NamespaceOnly(const Descriptor* desc) { |
| 1300 | return false; |
| 1301 | } |
| 1302 | |
| 1303 | void Generator::FindRequiresForMessage( |
| 1304 | const GeneratorOptions& options, |
| 1305 | const Descriptor* desc, |
| 1306 | std::set<string>* required, |
| 1307 | std::set<string>* forwards, |
| 1308 | bool* have_message) const { |
| 1309 | |
| 1310 | |
| 1311 | if (!NamespaceOnly(desc)) { |
| 1312 | *have_message = true; |
| 1313 | for (int i = 0; i < desc->field_count(); i++) { |
| 1314 | const FieldDescriptor* field = desc->field(i); |
| 1315 | if (IgnoreField(field)) { |
| 1316 | continue; |
| 1317 | } |
| 1318 | FindRequiresForField(options, field, required, forwards); |
| 1319 | } |
| 1320 | } |
| 1321 | |
| 1322 | for (int i = 0; i < desc->extension_count(); i++) { |
| 1323 | const FieldDescriptor* field = desc->extension(i); |
| 1324 | if (IgnoreField(field)) { |
| 1325 | continue; |
| 1326 | } |
| 1327 | FindRequiresForExtension(options, field, required, forwards); |
| 1328 | } |
| 1329 | |
| 1330 | for (int i = 0; i < desc->nested_type_count(); i++) { |
| 1331 | FindRequiresForMessage(options, desc->nested_type(i), required, forwards, |
| 1332 | have_message); |
| 1333 | } |
| 1334 | } |
| 1335 | |
| 1336 | void Generator::FindRequiresForField(const GeneratorOptions& options, |
| 1337 | const FieldDescriptor* field, |
| 1338 | std::set<string>* required, |
| 1339 | std::set<string>* forwards) const { |
| 1340 | if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM && |
| 1341 | // N.B.: file-level extensions with enum type do *not* create |
| 1342 | // dependencies, as per original codegen. |
| 1343 | !(field->is_extension() && field->extension_scope() == NULL)) { |
| 1344 | if (options.add_require_for_enums) { |
| 1345 | required->insert(GetPath(options, field->enum_type())); |
| 1346 | } else { |
| 1347 | forwards->insert(GetPath(options, field->enum_type())); |
| 1348 | } |
| 1349 | } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { |
| 1350 | required->insert(GetPath(options, field->message_type())); |
| 1351 | } |
| 1352 | } |
| 1353 | |
| 1354 | void Generator::FindRequiresForExtension(const GeneratorOptions& options, |
| 1355 | const FieldDescriptor* field, |
| 1356 | std::set<string>* required, |
| 1357 | std::set<string>* forwards) const { |
| 1358 | if (field->containing_type()->full_name() != "google.protobuf.bridge.MessageSet") { |
| 1359 | required->insert(GetPath(options, field->containing_type())); |
| 1360 | } |
| 1361 | FindRequiresForField(options, field, required, forwards); |
| 1362 | } |
| 1363 | |
| 1364 | void Generator::GenerateTestOnly(const GeneratorOptions& options, |
| 1365 | io::Printer* printer) const { |
| 1366 | if (options.testonly) { |
| 1367 | printer->Print("goog.setTestOnly();\n\n"); |
| 1368 | } |
| 1369 | printer->Print("\n"); |
| 1370 | } |
| 1371 | |
| 1372 | void Generator::GenerateClassesAndEnums(const GeneratorOptions& options, |
| 1373 | io::Printer* printer, |
| 1374 | const FileDescriptor* file) const { |
| 1375 | for (int i = 0; i < file->message_type_count(); i++) { |
| 1376 | GenerateClass(options, printer, file->message_type(i)); |
| 1377 | } |
| 1378 | for (int i = 0; i < file->enum_type_count(); i++) { |
| 1379 | GenerateEnum(options, printer, file->enum_type(i)); |
| 1380 | } |
| 1381 | } |
| 1382 | |
| 1383 | void Generator::GenerateClass(const GeneratorOptions& options, |
| 1384 | io::Printer* printer, |
| 1385 | const Descriptor* desc) const { |
| 1386 | if (!NamespaceOnly(desc)) { |
| 1387 | printer->Print("\n"); |
| 1388 | GenerateClassConstructor(options, printer, desc); |
| 1389 | GenerateClassFieldInfo(options, printer, desc); |
| 1390 | |
| 1391 | |
| 1392 | GenerateClassToObject(options, printer, desc); |
| 1393 | if (options.binary) { |
| 1394 | // These must come *before* the extension-field info generation in |
| 1395 | // GenerateClassRegistration so that references to the binary |
| 1396 | // serialization/deserialization functions may be placed in the extension |
| 1397 | // objects. |
| 1398 | GenerateClassDeserializeBinary(options, printer, desc); |
| 1399 | GenerateClassSerializeBinary(options, printer, desc); |
| 1400 | } |
| 1401 | GenerateClassClone(options, printer, desc); |
| 1402 | GenerateClassRegistration(options, printer, desc); |
| 1403 | GenerateClassFields(options, printer, desc); |
| 1404 | if (IsExtendable(desc) && desc->full_name() != "google.protobuf.bridge.MessageSet") { |
| 1405 | GenerateClassExtensionFieldInfo(options, printer, desc); |
| 1406 | } |
| 1407 | } |
| 1408 | |
| 1409 | // Recurse on nested types. |
| 1410 | for (int i = 0; i < desc->enum_type_count(); i++) { |
| 1411 | GenerateEnum(options, printer, desc->enum_type(i)); |
| 1412 | } |
| 1413 | for (int i = 0; i < desc->nested_type_count(); i++) { |
| 1414 | GenerateClass(options, printer, desc->nested_type(i)); |
| 1415 | } |
| 1416 | } |
| 1417 | |
| 1418 | void Generator::GenerateClassConstructor(const GeneratorOptions& options, |
| 1419 | io::Printer* printer, |
| 1420 | const Descriptor* desc) const { |
| 1421 | printer->Print( |
| 1422 | "/**\n" |
| 1423 | " * Generated by JsPbCodeGenerator.\n" |
| 1424 | " * @param {Array=} opt_data Optional initial data array, typically " |
| 1425 | "from a\n" |
| 1426 | " * server response, or constructed directly in Javascript. The array " |
| 1427 | "is used\n" |
| 1428 | " * in place and becomes part of the constructed object. It is not " |
| 1429 | "cloned.\n" |
| 1430 | " * If no data is provided, the constructed object will be empty, but " |
| 1431 | "still\n" |
| 1432 | " * valid.\n" |
| 1433 | " * @extends {jspb.Message}\n" |
| 1434 | " * @constructor\n" |
| 1435 | " */\n" |
| 1436 | "$classname$ = function(opt_data) {\n", |
| 1437 | "classname", GetPath(options, desc)); |
| 1438 | string message_id = GetMessageId(desc); |
| 1439 | printer->Print( |
| 1440 | " jspb.Message.initialize(this, opt_data, $messageId$, $pivot$, " |
| 1441 | "$rptfields$, $oneoffields$);\n", |
| 1442 | "messageId", !message_id.empty() ? |
| 1443 | ("'" + message_id + "'") : |
| 1444 | (IsResponse(desc) ? "''" : "0"), |
| 1445 | "pivot", GetPivot(desc), |
| 1446 | "rptfields", RepeatedFieldsArrayName(options, desc), |
| 1447 | "oneoffields", OneofFieldsArrayName(options, desc)); |
| 1448 | printer->Print( |
| 1449 | "};\n" |
| 1450 | "goog.inherits($classname$, jspb.Message);\n" |
| 1451 | "if (goog.DEBUG && !COMPILED) {\n" |
| 1452 | " $classname$.displayName = '$classname$';\n" |
| 1453 | "}\n", |
| 1454 | "classname", GetPath(options, desc)); |
| 1455 | } |
| 1456 | |
| 1457 | void Generator::GenerateClassFieldInfo(const GeneratorOptions& options, |
| 1458 | io::Printer* printer, |
| 1459 | const Descriptor* desc) const { |
| 1460 | if (HasRepeatedFields(desc)) { |
| 1461 | printer->Print( |
| 1462 | "/**\n" |
| 1463 | " * List of repeated fields within this message type.\n" |
| 1464 | " * @private {!Array<number>}\n" |
| 1465 | " * @const\n" |
| 1466 | " */\n" |
| 1467 | "$classname$$rptfieldarray$ = $rptfields$;\n" |
| 1468 | "\n", |
| 1469 | "classname", GetPath(options, desc), |
| 1470 | "rptfieldarray", kRepeatedFieldArrayName, |
| 1471 | "rptfields", RepeatedFieldNumberList(desc)); |
| 1472 | } |
| 1473 | |
| 1474 | if (HasOneofFields(desc)) { |
| 1475 | printer->Print( |
| 1476 | "/**\n" |
| 1477 | " * Oneof group definitions for this message. Each group defines the " |
| 1478 | "field\n" |
| 1479 | " * numbers belonging to that group. When of these fields' value is " |
| 1480 | "set, all\n" |
| 1481 | " * other fields in the group are cleared. During deserialization, if " |
| 1482 | "multiple\n" |
| 1483 | " * fields are encountered for a group, only the last value seen will " |
| 1484 | "be kept.\n" |
| 1485 | " * @private {!Array<!Array<number>>}\n" |
| 1486 | " * @const\n" |
| 1487 | " */\n" |
| 1488 | "$classname$$oneofgrouparray$ = $oneofgroups$;\n" |
| 1489 | "\n", |
| 1490 | "classname", GetPath(options, desc), |
| 1491 | "oneofgrouparray", kOneofGroupArrayName, |
| 1492 | "oneofgroups", OneofGroupList(desc)); |
| 1493 | |
| 1494 | for (int i = 0; i < desc->oneof_decl_count(); i++) { |
| 1495 | if (IgnoreOneof(desc->oneof_decl(i))) { |
| 1496 | continue; |
| 1497 | } |
| 1498 | GenerateOneofCaseDefinition(options, printer, desc->oneof_decl(i)); |
| 1499 | } |
| 1500 | } |
| 1501 | } |
| 1502 | |
| 1503 | void Generator::GenerateClassXid(const GeneratorOptions& options, |
| 1504 | io::Printer* printer, |
| 1505 | const Descriptor* desc) const { |
| 1506 | printer->Print( |
| 1507 | "\n" |
| 1508 | "\n" |
| 1509 | "$class$.prototype.messageXid = xid('$class$');\n", |
| 1510 | "class", GetPath(options, desc)); |
| 1511 | } |
| 1512 | |
| 1513 | void Generator::GenerateOneofCaseDefinition( |
| 1514 | const GeneratorOptions& options, |
| 1515 | io::Printer* printer, |
| 1516 | const OneofDescriptor* oneof) const { |
| 1517 | printer->Print( |
| 1518 | "/**\n" |
| 1519 | " * @enum {number}\n" |
| 1520 | " */\n" |
| 1521 | "$classname$.$oneof$Case = {\n" |
| 1522 | " $upcase$_NOT_SET: 0", |
| 1523 | "classname", GetPath(options, oneof->containing_type()), |
| 1524 | "oneof", JSOneofName(oneof), |
| 1525 | "upcase", ToEnumCase(oneof->name())); |
| 1526 | |
| 1527 | for (int i = 0; i < oneof->field_count(); i++) { |
| 1528 | if (IgnoreField(oneof->field(i))) { |
| 1529 | continue; |
| 1530 | } |
| 1531 | |
| 1532 | printer->Print( |
| 1533 | ",\n" |
| 1534 | " $upcase$: $number$", |
| 1535 | "upcase", ToEnumCase(oneof->field(i)->name()), |
| 1536 | "number", JSFieldIndex(oneof->field(i))); |
| 1537 | } |
| 1538 | |
| 1539 | printer->Print( |
| 1540 | "\n" |
| 1541 | "};\n" |
| 1542 | "\n" |
| 1543 | "/**\n" |
| 1544 | " * @return {$class$.$oneof$Case}\n" |
| 1545 | " */\n" |
| 1546 | "$class$.prototype.get$oneof$Case = function() {\n" |
| 1547 | " return /** @type {$class$.$oneof$Case} */(jspb.Message." |
| 1548 | "computeOneofCase(this, $class$.oneofGroups_[$oneofindex$]));\n" |
| 1549 | "};\n" |
| 1550 | "\n", |
| 1551 | "class", GetPath(options, oneof->containing_type()), |
| 1552 | "oneof", JSOneofName(oneof), |
| 1553 | "oneofindex", JSOneofIndex(oneof)); |
| 1554 | } |
| 1555 | |
| 1556 | void Generator::GenerateClassToObject(const GeneratorOptions& options, |
| 1557 | io::Printer* printer, |
| 1558 | const Descriptor* desc) const { |
| 1559 | printer->Print( |
| 1560 | "\n" |
| 1561 | "\n" |
| 1562 | "if (jspb.Message.GENERATE_TO_OBJECT) {\n" |
| 1563 | "/**\n" |
| 1564 | " * Creates an object representation of this proto suitable for use in " |
| 1565 | "Soy templates.\n" |
| 1566 | " * Field names that are reserved in JavaScript and will be renamed to " |
| 1567 | "pb_name.\n" |
| 1568 | " * To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.\n" |
| 1569 | " * For the list of reserved names please see:\n" |
| 1570 | " * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.\n" |
| 1571 | " * @param {boolean=} opt_includeInstance Whether to include the JSPB " |
| 1572 | "instance\n" |
| 1573 | " * for transitional soy proto support: http://goto/soy-param-" |
| 1574 | "migration\n" |
| 1575 | " * @return {!Object}\n" |
| 1576 | " */\n" |
| 1577 | "$classname$.prototype.toObject = function(opt_includeInstance) {\n" |
| 1578 | " return $classname$.toObject(opt_includeInstance, this);\n" |
| 1579 | "};\n" |
| 1580 | "\n" |
| 1581 | "\n" |
| 1582 | "/**\n" |
| 1583 | " * Static version of the {@see toObject} method.\n" |
| 1584 | " * @param {boolean|undefined} includeInstance Whether to include the " |
| 1585 | "JSPB\n" |
| 1586 | " * instance for transitional soy proto support:\n" |
| 1587 | " * http://goto/soy-param-migration\n" |
| 1588 | " * @param {!$classname$} msg The msg instance to transform.\n" |
| 1589 | " * @return {!Object}\n" |
| 1590 | " */\n" |
| 1591 | "$classname$.toObject = function(includeInstance, msg) {\n" |
| 1592 | " var f, obj = {", |
| 1593 | "classname", GetPath(options, desc)); |
| 1594 | |
| 1595 | bool first = true; |
| 1596 | for (int i = 0; i < desc->field_count(); i++) { |
| 1597 | const FieldDescriptor* field = desc->field(i); |
| 1598 | if (IgnoreField(field)) { |
| 1599 | continue; |
| 1600 | } |
| 1601 | |
| 1602 | if (!first) { |
| 1603 | printer->Print(",\n "); |
| 1604 | } else { |
| 1605 | printer->Print("\n "); |
| 1606 | first = false; |
| 1607 | } |
| 1608 | |
| 1609 | GenerateClassFieldToObject(options, printer, field); |
| 1610 | } |
| 1611 | |
| 1612 | if (!first) { |
| 1613 | printer->Print("\n };\n\n"); |
| 1614 | } else { |
| 1615 | printer->Print("\n\n };\n\n"); |
| 1616 | } |
| 1617 | |
| 1618 | if (IsExtendable(desc)) { |
| 1619 | printer->Print( |
| 1620 | " jspb.Message.toObjectExtension(/** @type {!jspb.Message} */ (msg), " |
| 1621 | "obj,\n" |
| 1622 | " $extObject$, $class$.prototype.getExtension,\n" |
| 1623 | " includeInstance);\n", |
| 1624 | "extObject", JSExtensionsObjectName(options, desc), |
| 1625 | "class", GetPath(options, desc)); |
| 1626 | } |
| 1627 | |
| 1628 | printer->Print( |
| 1629 | " if (includeInstance) {\n" |
| 1630 | " obj.$$jspbMessageInstance = msg\n" |
| 1631 | " }\n" |
| 1632 | " return obj;\n" |
| 1633 | "};\n" |
| 1634 | "}\n" |
| 1635 | "\n" |
| 1636 | "\n", |
| 1637 | "classname", GetPath(options, desc)); |
| 1638 | } |
| 1639 | |
| 1640 | void Generator::GenerateClassFieldToObject(const GeneratorOptions& options, |
| 1641 | io::Printer* printer, |
| 1642 | const FieldDescriptor* field) const { |
| 1643 | printer->Print("$fieldname$: ", |
| 1644 | "fieldname", JSObjectFieldName(field)); |
| 1645 | |
| 1646 | if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { |
| 1647 | // Message field. |
| 1648 | if (field->is_repeated()) { |
| 1649 | { |
| 1650 | printer->Print("jspb.Message.toObjectList(msg.get$getter$(),\n" |
| 1651 | " $type$.toObject, includeInstance)", |
| 1652 | "getter", JSGetterName(field), |
| 1653 | "type", GetPath(options, field->message_type())); |
| 1654 | } |
| 1655 | } else { |
| 1656 | printer->Print("(f = msg.get$getter$()) && " |
| 1657 | "$type$.toObject(includeInstance, f)", |
| 1658 | "getter", JSGetterName(field), |
| 1659 | "type", GetPath(options, field->message_type())); |
| 1660 | } |
| 1661 | } else { |
| 1662 | // Simple field (singular or repeated). |
| 1663 | if (!HasFieldPresence(field) && !field->is_repeated()) { |
| 1664 | // Delegate to the generated get<field>() method in order not to duplicate |
| 1665 | // the proto3-field-default-value logic here. |
| 1666 | printer->Print("msg.get$getter$()", |
| 1667 | "getter", JSGetterName(field)); |
| 1668 | } else { |
| 1669 | if (field->has_default_value()) { |
| 1670 | printer->Print("jspb.Message.getField(msg, $index$) != null ? " |
| 1671 | "jspb.Message.getField(msg, $index$) : $defaultValue$", |
| 1672 | "index", JSFieldIndex(field), |
| 1673 | "defaultValue", JSFieldDefault(field)); |
| 1674 | } else { |
| 1675 | printer->Print("jspb.Message.getField(msg, $index$)", |
| 1676 | "index", JSFieldIndex(field)); |
| 1677 | } |
| 1678 | } |
| 1679 | } |
| 1680 | } |
| 1681 | |
| 1682 | void Generator::GenerateClassFromObject(const GeneratorOptions& options, |
| 1683 | io::Printer* printer, |
| 1684 | const Descriptor* desc) const { |
| 1685 | printer->Print( |
| 1686 | "if (jspb.Message.GENERATE_FROM_OBJECT) {\n" |
| 1687 | "/**\n" |
| 1688 | " * Loads data from an object into a new instance of this proto.\n" |
| 1689 | " * @param {!Object} obj The object representation of this proto to\n" |
| 1690 | " * load the data from.\n" |
| 1691 | " * @return {!$classname$}\n" |
| 1692 | " */\n" |
| 1693 | "$classname$.fromObject = function(obj) {\n" |
| 1694 | " var f, msg = new $classname$();\n", |
| 1695 | "classname", GetPath(options, desc)); |
| 1696 | |
| 1697 | for (int i = 0; i < desc->field_count(); i++) { |
| 1698 | const FieldDescriptor* field = desc->field(i); |
| 1699 | GenerateClassFieldFromObject(options, printer, field); |
| 1700 | } |
| 1701 | |
| 1702 | printer->Print( |
| 1703 | " return msg;\n" |
| 1704 | "};\n" |
| 1705 | "}\n"); |
| 1706 | } |
| 1707 | |
| 1708 | void Generator::GenerateClassFieldFromObject( |
| 1709 | const GeneratorOptions& options, |
| 1710 | io::Printer* printer, |
| 1711 | const FieldDescriptor* field) const { |
| 1712 | if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { |
| 1713 | // Message field (singular or repeated) |
| 1714 | if (field->is_repeated()) { |
| 1715 | { |
| 1716 | printer->Print( |
| 1717 | " goog.isDef(obj.$name$) && " |
| 1718 | "jspb.Message.setRepeatedWrapperField(\n" |
| 1719 | " msg, $index$, goog.array.map(obj.$name$, function(i) {\n" |
| 1720 | " return $fieldclass$.fromObject(i);\n" |
| 1721 | " }));\n", |
| 1722 | "name", JSObjectFieldName(field), |
| 1723 | "index", JSFieldIndex(field), |
| 1724 | "fieldclass", GetPath(options, field->message_type())); |
| 1725 | } |
| 1726 | } else { |
| 1727 | printer->Print( |
| 1728 | " goog.isDef(obj.$name$) && jspb.Message.setWrapperField(\n" |
| 1729 | " msg, $index$, $fieldclass$.fromObject(obj.$name$));\n", |
| 1730 | "name", JSObjectFieldName(field), |
| 1731 | "index", JSFieldIndex(field), |
| 1732 | "fieldclass", GetPath(options, field->message_type())); |
| 1733 | } |
| 1734 | } else { |
| 1735 | // Simple (primitive) field. |
| 1736 | printer->Print( |
| 1737 | " goog.isDef(obj.$name$) && jspb.Message.setField(msg, $index$, " |
| 1738 | "obj.$name$);\n", |
| 1739 | "name", JSObjectFieldName(field), |
| 1740 | "index", JSFieldIndex(field)); |
| 1741 | } |
| 1742 | } |
| 1743 | |
| 1744 | void Generator::GenerateClassClone(const GeneratorOptions& options, |
| 1745 | io::Printer* printer, |
| 1746 | const Descriptor* desc) const { |
| 1747 | printer->Print( |
| 1748 | "/**\n" |
| 1749 | " * Creates a deep clone of this proto. No data is shared with the " |
| 1750 | "original.\n" |
| 1751 | " * @return {!$name$} The clone.\n" |
| 1752 | " */\n" |
| 1753 | "$name$.prototype.cloneMessage = function() {\n" |
| 1754 | " return /** @type {!$name$} */ (jspb.Message.cloneMessage(this));\n" |
| 1755 | "};\n\n\n", |
| 1756 | "name", GetPath(options, desc)); |
| 1757 | } |
| 1758 | |
| 1759 | void Generator::GenerateClassRegistration(const GeneratorOptions& options, |
| 1760 | io::Printer* printer, |
| 1761 | const Descriptor* desc) const { |
| 1762 | // Register any extensions defined inside this message type. |
| 1763 | for (int i = 0; i < desc->extension_count(); i++) { |
| 1764 | const FieldDescriptor* extension = desc->extension(i); |
| 1765 | if (ShouldGenerateExtension(extension)) { |
| 1766 | GenerateExtension(options, printer, extension); |
| 1767 | } |
| 1768 | } |
| 1769 | |
| 1770 | } |
| 1771 | |
| 1772 | void Generator::GenerateClassFields(const GeneratorOptions& options, |
| 1773 | io::Printer* printer, |
| 1774 | const Descriptor* desc) const { |
| 1775 | for (int i = 0; i < desc->field_count(); i++) { |
| 1776 | if (!IgnoreField(desc->field(i))) { |
| 1777 | GenerateClassField(options, printer, desc->field(i)); |
| 1778 | } |
| 1779 | } |
| 1780 | } |
| 1781 | |
| 1782 | void Generator::GenerateClassField(const GeneratorOptions& options, |
| 1783 | io::Printer* printer, |
| 1784 | const FieldDescriptor* field) const { |
| 1785 | if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { |
| 1786 | printer->Print( |
| 1787 | "/**\n" |
| 1788 | " * $fielddef$\n" |
| 1789 | "$comment$" |
| 1790 | " * @return {$type$}\n" |
| 1791 | " */\n", |
| 1792 | "fielddef", FieldDefinition(options, field), |
| 1793 | "comment", FieldComments(field), |
| 1794 | "type", JSFieldTypeAnnotation(options, field, |
| 1795 | /* force_optional = */ false, |
| 1796 | /* force_present = */ false, |
| 1797 | /* singular_if_not_packed = */ false, |
| 1798 | /* always_singular = */ false)); |
| 1799 | printer->Print( |
| 1800 | "$class$.prototype.get$name$ = function() {\n" |
| 1801 | " return /** @type{$type$} */ (\n" |
| 1802 | " jspb.Message.get$rpt$WrapperField(this, $wrapperclass$, " |
| 1803 | "$index$$required$));\n" |
| 1804 | "};\n" |
| 1805 | "\n" |
| 1806 | "\n", |
| 1807 | "class", GetPath(options, field->containing_type()), |
| 1808 | "name", JSGetterName(field), |
| 1809 | "type", JSFieldTypeAnnotation(options, field, |
| 1810 | /* force_optional = */ false, |
| 1811 | /* force_present = */ false, |
| 1812 | /* singular_if_not_packed = */ false, |
| 1813 | /* always_singular = */ false), |
| 1814 | "rpt", (field->is_repeated() ? "Repeated" : ""), |
| 1815 | "index", JSFieldIndex(field), |
| 1816 | "wrapperclass", GetPath(options, field->message_type()), |
| 1817 | "required", (field->label() == FieldDescriptor::LABEL_REQUIRED ? |
| 1818 | ", 1" : "")); |
| 1819 | printer->Print( |
| 1820 | "/** @param {$optionaltype$} value $returndoc$ */\n" |
| 1821 | "$class$.prototype.set$name$ = function(value) {\n" |
| 1822 | " jspb.Message.set$oneoftag$$repeatedtag$WrapperField(", |
| 1823 | "optionaltype", |
| 1824 | JSFieldTypeAnnotation(options, field, |
| 1825 | /* force_optional = */ true, |
| 1826 | /* force_present = */ false, |
| 1827 | /* singular_if_not_packed = */ false, |
| 1828 | /* always_singular = */ false), |
| 1829 | "returndoc", JSReturnDoc(options, field), |
| 1830 | "class", GetPath(options, field->containing_type()), |
| 1831 | "name", JSGetterName(field), |
| 1832 | "oneoftag", (field->containing_oneof() ? "Oneof" : ""), |
| 1833 | "repeatedtag", (field->is_repeated() ? "Repeated" : "")); |
| 1834 | |
| 1835 | printer->Print( |
| 1836 | "this, $index$$oneofgroup$, value);$returnvalue$\n" |
| 1837 | "};\n" |
| 1838 | "\n" |
| 1839 | "\n", |
| 1840 | "index", JSFieldIndex(field), |
| 1841 | "oneofgroup", (field->containing_oneof() ? |
| 1842 | (", " + JSOneofArray(options, field)) : ""), |
| 1843 | "returnvalue", JSReturnClause(field)); |
| 1844 | |
| 1845 | printer->Print( |
| 1846 | "$class$.prototype.clear$name$ = function() {\n" |
| 1847 | " this.set$name$($clearedvalue$);$returnvalue$\n" |
| 1848 | "};\n" |
| 1849 | "\n" |
| 1850 | "\n", |
| 1851 | "class", GetPath(options, field->containing_type()), |
| 1852 | "name", JSGetterName(field), |
| 1853 | "clearedvalue", (field->is_repeated() ? "[]" : "undefined"), |
| 1854 | "returnvalue", JSReturnClause(field)); |
| 1855 | |
| 1856 | } else { |
| 1857 | string typed_annotation; |
| 1858 | |
| 1859 | // Simple (primitive) field, either singular or repeated. |
| 1860 | { |
| 1861 | typed_annotation = JSFieldTypeAnnotation(options, field, |
| 1862 | /* force_optional = */ false, |
| 1863 | /* force_present = */ !HasFieldPresence(field), |
| 1864 | /* singular_if_not_packed = */ false, |
| 1865 | /* always_singular = */ false), |
| 1866 | printer->Print( |
| 1867 | "/**\n" |
| 1868 | " * $fielddef$\n" |
| 1869 | "$comment$" |
| 1870 | " * @return {$type$}\n" |
| 1871 | " */\n", |
| 1872 | "fielddef", FieldDefinition(options, field), |
| 1873 | "comment", FieldComments(field), |
| 1874 | "type", typed_annotation); |
| 1875 | } |
| 1876 | |
| 1877 | printer->Print( |
| 1878 | "$class$.prototype.get$name$ = function() {\n", |
| 1879 | "class", GetPath(options, field->containing_type()), |
| 1880 | "name", JSGetterName(field)); |
| 1881 | |
| 1882 | { |
| 1883 | printer->Print( |
| 1884 | " return /** @type {$type$} */ (", |
| 1885 | "type", typed_annotation); |
| 1886 | } |
| 1887 | |
| 1888 | // For proto3 fields without presence, use special getters that will return |
| 1889 | // defaults when the field is unset, possibly constructing a value if |
| 1890 | // required. |
| 1891 | if (!HasFieldPresence(field) && !field->is_repeated()) { |
| 1892 | printer->Print("jspb.Message.getFieldProto3(this, $index$, $default$)", |
| 1893 | "index", JSFieldIndex(field), |
| 1894 | "default", Proto3PrimitiveFieldDefault(field)); |
| 1895 | } else { |
| 1896 | if (field->has_default_value()) { |
| 1897 | printer->Print("jspb.Message.getField(this, $index$) != null ? " |
| 1898 | "jspb.Message.getField(this, $index$) : $defaultValue$", |
| 1899 | "index", JSFieldIndex(field), |
| 1900 | "defaultValue", JSFieldDefault(field)); |
| 1901 | } else { |
| 1902 | printer->Print("jspb.Message.getField(this, $index$)", |
| 1903 | "index", JSFieldIndex(field)); |
| 1904 | } |
| 1905 | } |
| 1906 | |
| 1907 | { |
| 1908 | printer->Print( |
| 1909 | ");\n" |
| 1910 | "};\n" |
| 1911 | "\n" |
| 1912 | "\n"); |
| 1913 | } |
| 1914 | |
| 1915 | { |
| 1916 | printer->Print( |
| 1917 | "/** @param {$optionaltype$} value $returndoc$ */\n", |
| 1918 | "optionaltype", |
| 1919 | JSFieldTypeAnnotation(options, field, |
| 1920 | /* force_optional = */ true, |
| 1921 | /* force_present = */ !HasFieldPresence(field), |
| 1922 | /* singular_if_not_packed = */ false, |
| 1923 | /* always_singular = */ false), |
| 1924 | "returndoc", JSReturnDoc(options, field)); |
| 1925 | } |
| 1926 | |
| 1927 | printer->Print( |
| 1928 | "$class$.prototype.set$name$ = function(value) {\n" |
| 1929 | " jspb.Message.set$oneoftag$Field(this, $index$", |
| 1930 | "class", GetPath(options, field->containing_type()), |
| 1931 | "name", JSGetterName(field), |
| 1932 | "oneoftag", (field->containing_oneof() ? "Oneof" : ""), |
| 1933 | "index", JSFieldIndex(field)); |
| 1934 | printer->Print( |
| 1935 | "$oneofgroup$, $type$value$rptvalueinit$$typeclose$);$returnvalue$\n" |
| 1936 | "};\n" |
| 1937 | "\n" |
| 1938 | "\n", |
| 1939 | "type", "", |
| 1940 | "typeclose", "", |
| 1941 | "oneofgroup", |
| 1942 | (field->containing_oneof() ? (", " + JSOneofArray(options, field)) |
| 1943 | : ""), |
| 1944 | "returnvalue", JSReturnClause(field), "rptvalueinit", |
| 1945 | (field->is_repeated() ? " || []" : "")); |
| 1946 | |
| 1947 | |
| 1948 | if (HasFieldPresence(field)) { |
| 1949 | printer->Print( |
| 1950 | "$class$.prototype.clear$name$ = function() {\n" |
| 1951 | " jspb.Message.set$oneoftag$Field(this, $index$$oneofgroup$, ", |
| 1952 | "class", GetPath(options, field->containing_type()), |
| 1953 | "name", JSGetterName(field), |
| 1954 | "oneoftag", (field->containing_oneof() ? "Oneof" : ""), |
| 1955 | "oneofgroup", (field->containing_oneof() ? |
| 1956 | (", " + JSOneofArray(options, field)) : ""), |
| 1957 | "index", JSFieldIndex(field)); |
| 1958 | printer->Print( |
| 1959 | "$clearedvalue$);$returnvalue$\n" |
| 1960 | "};\n" |
| 1961 | "\n" |
| 1962 | "\n", |
| 1963 | "clearedvalue", (field->is_repeated() ? "[]" : "undefined"), |
| 1964 | "returnvalue", JSReturnClause(field)); |
| 1965 | } |
| 1966 | } |
| 1967 | } |
| 1968 | |
| 1969 | void Generator::GenerateClassExtensionFieldInfo(const GeneratorOptions& options, |
| 1970 | io::Printer* printer, |
| 1971 | const Descriptor* desc) const { |
| 1972 | if (IsExtendable(desc)) { |
| 1973 | printer->Print( |
| 1974 | "\n" |
| 1975 | "/**\n" |
| 1976 | " * The extensions registered with this message class. This is a " |
| 1977 | "map of\n" |
| 1978 | " * extension field number to fieldInfo object.\n" |
| 1979 | " *\n" |
| 1980 | " * For example:\n" |
| 1981 | " * { 123: {fieldIndex: 123, fieldName: {my_field_name: 0}, " |
| 1982 | "ctor: proto.example.MyMessage} }\n" |
| 1983 | " *\n" |
| 1984 | " * fieldName contains the JsCompiler renamed field name property " |
| 1985 | "so that it\n" |
| 1986 | " * works in OPTIMIZED mode.\n" |
| 1987 | " *\n" |
| 1988 | " * @type {!Object.<number, jspb.ExtensionFieldInfo>}\n" |
| 1989 | " */\n" |
| 1990 | "$class$.extensions = {};\n" |
| 1991 | "\n", |
| 1992 | "class", GetPath(options, desc)); |
| 1993 | } |
| 1994 | } |
| 1995 | |
| 1996 | |
| 1997 | void Generator::GenerateClassDeserializeBinary(const GeneratorOptions& options, |
| 1998 | io::Printer* printer, |
| 1999 | const Descriptor* desc) const { |
| 2000 | // TODO(cfallin): Handle lazy decoding when requested by field option and/or |
| 2001 | // by default for 'bytes' fields and packed repeated fields. |
| 2002 | |
| 2003 | printer->Print( |
| 2004 | "/**\n" |
| 2005 | " * Deserializes binary data (in protobuf wire format).\n" |
| 2006 | " * @param {jspb.ByteSource} bytes The bytes to deserialize.\n" |
| 2007 | " * @return {!$class$}\n" |
| 2008 | " */\n" |
| 2009 | "$class$.deserializeBinary = function(bytes) {\n" |
| 2010 | " var reader = new jspb.BinaryReader(bytes);\n" |
| 2011 | " var msg = new $class$;\n" |
| 2012 | " return $class$.deserializeBinaryFromReader(msg, reader);\n" |
| 2013 | "};\n" |
| 2014 | "\n" |
| 2015 | "\n" |
| 2016 | "/**\n" |
| 2017 | " * Deserializes binary data (in protobuf wire format) from the\n" |
| 2018 | " * given reader into the given message object.\n" |
| 2019 | " * @param {!$class$} msg The message object to deserialize into.\n" |
| 2020 | " * @param {!jspb.BinaryReader} reader The BinaryReader to use.\n" |
| 2021 | " * @return {!$class$}\n" |
| 2022 | " */\n" |
| 2023 | "$class$.deserializeBinaryFromReader = function(msg, reader) {\n" |
| 2024 | " while (reader.nextField()) {\n" |
| 2025 | " if (reader.isEndGroup()) {\n" |
| 2026 | " break;\n" |
| 2027 | " }\n" |
| 2028 | " var field = reader.getFieldNumber();\n" |
| 2029 | " switch (field) {\n", |
| 2030 | "class", GetPath(options, desc)); |
| 2031 | |
| 2032 | for (int i = 0; i < desc->field_count(); i++) { |
| 2033 | GenerateClassDeserializeBinaryField(options, printer, desc->field(i)); |
| 2034 | } |
| 2035 | |
| 2036 | printer->Print( |
| 2037 | " default:\n"); |
| 2038 | if (IsExtendable(desc)) { |
| 2039 | printer->Print( |
| 2040 | " jspb.Message.readBinaryExtension(msg, reader, $extobj$,\n" |
| 2041 | " $class$.prototype.getExtension,\n" |
| 2042 | " $class$.prototype.setExtension);\n" |
| 2043 | " break;\n", |
| 2044 | "extobj", JSExtensionsObjectName(options, desc), |
| 2045 | "class", GetPath(options, desc)); |
| 2046 | } else { |
| 2047 | printer->Print( |
| 2048 | " reader.skipField();\n" |
| 2049 | " break;\n"); |
| 2050 | } |
| 2051 | |
| 2052 | printer->Print( |
| 2053 | " }\n" |
| 2054 | " }\n" |
| 2055 | " return msg;\n" |
| 2056 | "};\n" |
| 2057 | "\n" |
| 2058 | "\n"); |
| 2059 | } |
| 2060 | |
| 2061 | void Generator::GenerateClassDeserializeBinaryField( |
| 2062 | const GeneratorOptions& options, |
| 2063 | io::Printer* printer, |
| 2064 | const FieldDescriptor* field) const { |
| 2065 | |
| 2066 | printer->Print(" case $num$:\n", |
| 2067 | "num", SimpleItoa(field->number())); |
| 2068 | |
| 2069 | if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { |
| 2070 | printer->Print( |
| 2071 | " var value = new $fieldclass$;\n" |
| 2072 | " reader.read$msgOrGroup$($grpfield$value," |
| 2073 | "$fieldclass$.deserializeBinaryFromReader);\n", |
| 2074 | "fieldclass", GetPath(options, field->message_type()), |
| 2075 | "msgOrGroup", (field->type() == FieldDescriptor::TYPE_GROUP) ? |
| 2076 | "Group" : "Message", |
| 2077 | "grpfield", (field->type() == FieldDescriptor::TYPE_GROUP) ? |
| 2078 | (SimpleItoa(field->number()) + ", ") : ""); |
| 2079 | } else { |
| 2080 | printer->Print( |
| 2081 | " var value = /** @type {$fieldtype$} */ (reader.$reader$());\n", |
| 2082 | "fieldtype", JSFieldTypeAnnotation(options, field, false, true, |
| 2083 | /* singular_if_not_packed = */ true, |
| 2084 | /* always_singular = */ false), |
| 2085 | "reader", JSBinaryReaderMethodName(field)); |
| 2086 | } |
| 2087 | |
| 2088 | if (field->is_repeated() && !field->is_packed()) { |
| 2089 | // Repeated fields receive a |value| one at at a time; append to array |
| 2090 | // returned by get$name$(). |
| 2091 | printer->Print( |
| 2092 | " msg.get$name$().push(value);\n", |
| 2093 | "name", JSGetterName(field)); |
| 2094 | } else { |
| 2095 | // Singular fields, and packed repeated fields, receive a |value| either as |
| 2096 | // the field's value or as the array of all the field's values; set this as |
| 2097 | // the field's value directly. |
| 2098 | printer->Print( |
| 2099 | " msg.set$name$(value);\n", |
| 2100 | "name", JSGetterName(field)); |
| 2101 | } |
| 2102 | |
| 2103 | printer->Print(" break;\n"); |
| 2104 | } |
| 2105 | |
| 2106 | void Generator::GenerateClassSerializeBinary(const GeneratorOptions& options, |
| 2107 | io::Printer* printer, |
| 2108 | const Descriptor* desc) const { |
| 2109 | printer->Print( |
| 2110 | "/**\n" |
| 2111 | " * Class method variant: serializes the given message to binary data\n" |
| 2112 | " * (in protobuf wire format), writing to the given BinaryWriter.\n" |
| 2113 | " * @param {!$class$} message\n" |
| 2114 | " * @param {!jspb.BinaryWriter} writer\n" |
| 2115 | " */\n" |
| 2116 | "$class$.serializeBinaryToWriter = function(message, " |
| 2117 | "writer) {\n" |
| 2118 | " message.serializeBinaryToWriter(writer);\n" |
| 2119 | "};\n" |
| 2120 | "\n" |
| 2121 | "\n" |
| 2122 | "/**\n" |
| 2123 | " * Serializes the message to binary data (in protobuf wire format).\n" |
| 2124 | " * @return {!Uint8Array}\n" |
| 2125 | " */\n" |
| 2126 | "$class$.prototype.serializeBinary = function() {\n" |
| 2127 | " var writer = new jspb.BinaryWriter();\n" |
| 2128 | " this.serializeBinaryToWriter(writer);\n" |
| 2129 | " return writer.getResultBuffer();\n" |
| 2130 | "};\n" |
| 2131 | "\n" |
| 2132 | "\n" |
| 2133 | "/**\n" |
| 2134 | " * Serializes the message to binary data (in protobuf wire format),\n" |
| 2135 | " * writing to the given BinaryWriter.\n" |
| 2136 | " * @param {!jspb.BinaryWriter} writer\n" |
| 2137 | " */\n" |
| 2138 | "$class$.prototype.serializeBinaryToWriter = function (writer) {\n" |
| 2139 | " var f = undefined;\n", |
| 2140 | "class", GetPath(options, desc)); |
| 2141 | |
| 2142 | for (int i = 0; i < desc->field_count(); i++) { |
| 2143 | GenerateClassSerializeBinaryField(options, printer, desc->field(i)); |
| 2144 | } |
| 2145 | |
| 2146 | if (IsExtendable(desc)) { |
| 2147 | printer->Print( |
| 2148 | " jspb.Message.serializeBinaryExtensions(this, writer, $extobj$,\n" |
| 2149 | " $class$.prototype.getExtension);\n", |
| 2150 | "extobj", JSExtensionsObjectName(options, desc), |
| 2151 | "class", GetPath(options, desc)); |
| 2152 | } |
| 2153 | |
| 2154 | printer->Print( |
| 2155 | "};\n" |
| 2156 | "\n" |
| 2157 | "\n"); |
| 2158 | } |
| 2159 | |
| 2160 | void Generator::GenerateClassSerializeBinaryField( |
| 2161 | const GeneratorOptions& options, |
| 2162 | io::Printer* printer, |
| 2163 | const FieldDescriptor* field) const { |
| 2164 | printer->Print( |
| 2165 | " f = this.get$name$();\n", |
| 2166 | "name", JSGetterName(field)); |
| 2167 | |
| 2168 | if (field->is_repeated()) { |
| 2169 | printer->Print( |
| 2170 | " if (f.length > 0) {\n"); |
| 2171 | } else { |
| 2172 | if (HasFieldPresence(field)) { |
| 2173 | printer->Print( |
| 2174 | " if (f != null) {\n"); |
| 2175 | } else { |
| 2176 | // No field presence: serialize onto the wire only if value is |
| 2177 | // non-default. Defaults are documented here: |
| 2178 | // https://goto.google.com/lhdfm |
| 2179 | switch (field->cpp_type()) { |
| 2180 | case FieldDescriptor::CPPTYPE_INT32: |
| 2181 | case FieldDescriptor::CPPTYPE_INT64: |
| 2182 | case FieldDescriptor::CPPTYPE_UINT32: |
| 2183 | case FieldDescriptor::CPPTYPE_UINT64: { |
| 2184 | { |
| 2185 | printer->Print(" if (f !== 0) {\n"); |
| 2186 | } |
| 2187 | break; |
| 2188 | } |
| 2189 | |
| 2190 | case FieldDescriptor::CPPTYPE_ENUM: |
| 2191 | case FieldDescriptor::CPPTYPE_FLOAT: |
| 2192 | case FieldDescriptor::CPPTYPE_DOUBLE: |
| 2193 | printer->Print( |
| 2194 | " if (f !== 0.0) {\n"); |
| 2195 | break; |
| 2196 | case FieldDescriptor::CPPTYPE_BOOL: |
| 2197 | printer->Print( |
| 2198 | " if (f) {\n"); |
| 2199 | break; |
| 2200 | case FieldDescriptor::CPPTYPE_STRING: |
| 2201 | printer->Print( |
| 2202 | " if (f.length > 0) {\n"); |
| 2203 | break; |
| 2204 | default: |
| 2205 | assert(false); |
| 2206 | break; |
| 2207 | } |
| 2208 | } |
| 2209 | } |
| 2210 | |
| 2211 | printer->Print( |
| 2212 | " writer.$writer$(\n" |
| 2213 | " $index$,\n" |
| 2214 | " f", |
| 2215 | "writer", JSBinaryWriterMethodName(field), |
| 2216 | "name", JSGetterName(field), |
| 2217 | "index", SimpleItoa(field->number())); |
| 2218 | |
| 2219 | if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { |
| 2220 | printer->Print( |
| 2221 | ",\n" |
| 2222 | " $submsg$.serializeBinaryToWriter\n", |
| 2223 | "submsg", GetPath(options, field->message_type())); |
| 2224 | } else { |
| 2225 | printer->Print("\n"); |
| 2226 | } |
| 2227 | printer->Print( |
| 2228 | " );\n" |
| 2229 | " }\n"); |
| 2230 | } |
| 2231 | |
| 2232 | void Generator::GenerateEnum(const GeneratorOptions& options, |
| 2233 | io::Printer* printer, |
| 2234 | const EnumDescriptor* enumdesc) const { |
| 2235 | printer->Print( |
| 2236 | "/**\n" |
| 2237 | " * @enum {number}\n" |
| 2238 | " */\n" |
| 2239 | "$name$ = {\n", |
| 2240 | "name", GetPath(options, enumdesc)); |
| 2241 | |
| 2242 | for (int i = 0; i < enumdesc->value_count(); i++) { |
| 2243 | const EnumValueDescriptor* value = enumdesc->value(i); |
| 2244 | printer->Print( |
| 2245 | " $name$: $value$$comma$\n", |
| 2246 | "name", ToEnumCase(value->name()), |
| 2247 | "value", SimpleItoa(value->number()), |
| 2248 | "comma", (i == enumdesc->value_count() - 1) ? "" : ","); |
| 2249 | } |
| 2250 | |
| 2251 | printer->Print( |
| 2252 | "};\n" |
| 2253 | "\n"); |
| 2254 | } |
| 2255 | |
| 2256 | void Generator::GenerateExtension(const GeneratorOptions& options, |
| 2257 | io::Printer* printer, |
| 2258 | const FieldDescriptor* field) const { |
| 2259 | string extension_scope = |
| 2260 | (field->extension_scope() ? |
| 2261 | GetPath(options, field->extension_scope()) : |
| 2262 | GetPath(options, field->file())); |
| 2263 | |
| 2264 | printer->Print( |
| 2265 | "\n" |
| 2266 | "/**\n" |
| 2267 | " * A tuple of {field number, class constructor} for the extension\n" |
| 2268 | " * field named `$name$`.\n" |
| 2269 | " * @type {!jspb.ExtensionFieldInfo.<$extensionType$>}\n" |
| 2270 | " */\n" |
| 2271 | "$class$.$name$ = new jspb.ExtensionFieldInfo(\n", |
| 2272 | "name", JSObjectFieldName(field), |
| 2273 | "class", extension_scope, |
| 2274 | "extensionType", JSFieldTypeAnnotation( |
| 2275 | options, field, |
| 2276 | /* force_optional = */ false, |
| 2277 | /* force_present = */ true, |
| 2278 | /* singular_if_not_packed = */ false, |
| 2279 | /* always_singular = */ false)); |
| 2280 | printer->Print( |
| 2281 | " $index$,\n" |
| 2282 | " {$name$: 0},\n" |
| 2283 | " $ctor$,\n" |
| 2284 | " /** @type {?function((boolean|undefined),!jspb.Message=): " |
| 2285 | "!Object} */ (\n" |
| 2286 | " $toObject$),\n" |
| 2287 | " $repeated$", |
| 2288 | "index", SimpleItoa(field->number()), |
| 2289 | "name", JSObjectFieldName(field), |
| 2290 | "ctor", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE ? |
| 2291 | GetPath(options, field->message_type()) : string("null")), |
| 2292 | "toObject", (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE ? |
| 2293 | (GetPath(options, field->message_type()) + ".toObject") : |
| 2294 | string("null")), |
| 2295 | "repeated", (field->is_repeated() ? "1" : "0")); |
| 2296 | |
| 2297 | if (options.binary) { |
| 2298 | printer->Print( |
| 2299 | ",\n" |
| 2300 | " jspb.BinaryReader.prototype.$binaryReaderFn$,\n" |
| 2301 | " jspb.BinaryWriter.prototype.$binaryWriterFn$,\n" |
| 2302 | " $binaryMessageSerializeFn$,\n" |
| 2303 | " $binaryMessageDeserializeFn$,\n" |
| 2304 | " $isPacked$);\n", |
| 2305 | "binaryReaderFn", JSBinaryReaderMethodName(field), |
| 2306 | "binaryWriterFn", JSBinaryWriterMethodName(field), |
| 2307 | "binaryMessageSerializeFn", |
| 2308 | (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ? |
| 2309 | (GetPath(options, field->message_type()) + |
| 2310 | ".serializeBinaryToWriter") : "null", |
| 2311 | "binaryMessageDeserializeFn", |
| 2312 | (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ? |
| 2313 | (GetPath(options, field->message_type()) + |
| 2314 | ".deserializeBinaryFromReader") : "null", |
| 2315 | "isPacked", (field->is_packed() ? "true" : "false")); |
| 2316 | } else { |
| 2317 | printer->Print(");\n"); |
| 2318 | } |
| 2319 | |
| 2320 | printer->Print( |
| 2321 | "// This registers the extension field with the extended class, so that\n" |
| 2322 | "// toObject() will function correctly.\n" |
| 2323 | "$extendName$[$index$] = $class$.$name$;\n" |
| 2324 | "\n", |
| 2325 | "extendName", JSExtensionsObjectName(options, field->containing_type()), |
| 2326 | "index", SimpleItoa(field->number()), |
| 2327 | "class", extension_scope, |
| 2328 | "name", JSObjectFieldName(field)); |
| 2329 | } |
| 2330 | |
| 2331 | bool GeneratorOptions::ParseFromOptions( |
| 2332 | const vector< pair< string, string > >& options, |
| 2333 | string* error) { |
| 2334 | for (int i = 0; i < options.size(); i++) { |
| 2335 | if (options[i].first == "add_require_for_enums") { |
| 2336 | if (options[i].second != "") { |
| 2337 | *error = "Unexpected option value for add_require_for_enums"; |
| 2338 | return false; |
| 2339 | } |
| 2340 | add_require_for_enums = true; |
| 2341 | } else if (options[i].first == "binary") { |
| 2342 | if (options[i].second != "") { |
| 2343 | *error = "Unexpected option value for binary"; |
| 2344 | return false; |
| 2345 | } |
| 2346 | binary = true; |
| 2347 | } else if (options[i].first == "testonly") { |
| 2348 | if (options[i].second != "") { |
| 2349 | *error = "Unexpected option value for testonly"; |
| 2350 | return false; |
| 2351 | } |
| 2352 | testonly = true; |
| 2353 | } else if (options[i].first == "error_on_name_conflict") { |
| 2354 | if (options[i].second != "") { |
| 2355 | *error = "Unexpected option value for error_on_name_conflict"; |
| 2356 | return false; |
| 2357 | } |
| 2358 | error_on_name_conflict = true; |
| 2359 | } else if (options[i].first == "output_dir") { |
| 2360 | output_dir = options[i].second; |
| 2361 | } else if (options[i].first == "namespace_prefix") { |
| 2362 | namespace_prefix = options[i].second; |
| 2363 | } else if (options[i].first == "library") { |
| 2364 | library = options[i].second; |
| 2365 | } else { |
| 2366 | // Assume any other option is an output directory, as long as it is a bare |
| 2367 | // `key` rather than a `key=value` option. |
| 2368 | if (options[i].second != "") { |
| 2369 | *error = "Unknown option: " + options[i].first; |
| 2370 | return false; |
| 2371 | } |
| 2372 | output_dir = options[i].first; |
| 2373 | } |
| 2374 | } |
| 2375 | |
| 2376 | return true; |
| 2377 | } |
| 2378 | |
| 2379 | void Generator::GenerateFilesInDepOrder( |
| 2380 | const GeneratorOptions& options, |
| 2381 | io::Printer* printer, |
| 2382 | const vector<const FileDescriptor*>& files) const { |
| 2383 | // Build a std::set over all files so that the DFS can detect when it recurses |
| 2384 | // into a dep not specified in the user's command line. |
| 2385 | std::set<const FileDescriptor*> all_files(files.begin(), files.end()); |
| 2386 | // Track the in-progress set of files that have been generated already. |
| 2387 | std::set<const FileDescriptor*> generated; |
| 2388 | for (int i = 0; i < files.size(); i++) { |
| 2389 | GenerateFileAndDeps(options, printer, files[i], &all_files, &generated); |
| 2390 | } |
| 2391 | } |
| 2392 | |
| 2393 | void Generator::GenerateFileAndDeps( |
| 2394 | const GeneratorOptions& options, |
| 2395 | io::Printer* printer, |
| 2396 | const FileDescriptor* root, |
| 2397 | std::set<const FileDescriptor*>* all_files, |
| 2398 | std::set<const FileDescriptor*>* generated) const { |
| 2399 | // Skip if already generated. |
| 2400 | if (generated->find(root) != generated->end()) { |
| 2401 | return; |
| 2402 | } |
| 2403 | generated->insert(root); |
| 2404 | |
| 2405 | // Generate all dependencies before this file's content. |
| 2406 | for (int i = 0; i < root->dependency_count(); i++) { |
| 2407 | const FileDescriptor* dep = root->dependency(i); |
| 2408 | GenerateFileAndDeps(options, printer, dep, all_files, generated); |
| 2409 | } |
| 2410 | |
| 2411 | // Generate this file's content. Only generate if the file is part of the |
| 2412 | // original set requested to be generated; i.e., don't take all transitive |
| 2413 | // deps down to the roots. |
| 2414 | if (all_files->find(root) != all_files->end()) { |
| 2415 | GenerateClassesAndEnums(options, printer, root); |
| 2416 | } |
| 2417 | } |
| 2418 | |
| 2419 | bool Generator::GenerateAll(const vector<const FileDescriptor*>& files, |
| 2420 | const string& parameter, |
| 2421 | GeneratorContext* context, |
| 2422 | string* error) const { |
| 2423 | vector< pair< string, string > > option_pairs; |
| 2424 | ParseGeneratorParameter(parameter, &option_pairs); |
| 2425 | GeneratorOptions options; |
| 2426 | if (!options.ParseFromOptions(option_pairs, error)) { |
| 2427 | return false; |
| 2428 | } |
| 2429 | |
| 2430 | |
| 2431 | // We're either generating a single library file with definitions for message |
| 2432 | // and enum types in *all* FileDescriptor inputs, or we're generating a single |
| 2433 | // file for each type. |
| 2434 | if (options.library != "") { |
| 2435 | string filename = options.output_dir + "/" + options.library + ".js"; |
| 2436 | google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output(context->Open(filename)); |
| 2437 | GOOGLE_CHECK(output.get()); |
| 2438 | io::Printer printer(output.get(), '$'); |
| 2439 | |
| 2440 | // Pull out all extensions -- we need these to generate all |
| 2441 | // provides/requires. |
| 2442 | vector<const FieldDescriptor*> extensions; |
| 2443 | for (int i = 0; i < files.size(); i++) { |
| 2444 | for (int j = 0; j < files[i]->extension_count(); j++) { |
| 2445 | const FieldDescriptor* extension = files[i]->extension(j); |
| 2446 | extensions.push_back(extension); |
| 2447 | } |
| 2448 | } |
| 2449 | |
| 2450 | GenerateHeader(options, &printer); |
| 2451 | |
| 2452 | std::set<string> provided; |
| 2453 | FindProvides(options, &printer, files, &provided); |
| 2454 | FindProvidesForFields(options, &printer, extensions, &provided); |
| 2455 | GenerateProvides(options, &printer, &provided); |
| 2456 | GenerateTestOnly(options, &printer); |
| 2457 | GenerateRequires(options, &printer, files, &provided); |
| 2458 | |
| 2459 | GenerateFilesInDepOrder(options, &printer, files); |
| 2460 | |
| 2461 | for (int i = 0; i < extensions.size(); i++) { |
| 2462 | if (ShouldGenerateExtension(extensions[i])) { |
| 2463 | GenerateExtension(options, &printer, extensions[i]); |
| 2464 | } |
| 2465 | } |
| 2466 | |
| 2467 | if (printer.failed()) { |
| 2468 | return false; |
| 2469 | } |
| 2470 | } else { |
| 2471 | // Collect all types, and print each type to a separate file. Pull out |
| 2472 | // free-floating extensions while we make this pass. |
| 2473 | map< string, vector<const FieldDescriptor*> > extensions_by_namespace; |
| 2474 | |
| 2475 | // If we're generating code in file-per-type mode, avoid overwriting files |
| 2476 | // by choosing the last descriptor that writes each filename and permitting |
| 2477 | // only those to generate code. |
| 2478 | |
| 2479 | // Current descriptor that will generate each filename, indexed by filename. |
| 2480 | map<string, const void*> desc_by_filename; |
| 2481 | // Set of descriptors allowed to generate files. |
| 2482 | set<const void*> allowed_descs; |
| 2483 | |
| 2484 | for (int i = 0; i < files.size(); i++) { |
| 2485 | // Collect all (descriptor, filename) pairs. |
| 2486 | map<const void*, string> descs_in_file; |
| 2487 | for (int j = 0; j < files[i]->message_type_count(); j++) { |
| 2488 | const Descriptor* desc = files[i]->message_type(j); |
| 2489 | string filename = |
| 2490 | options.output_dir + "/" + ToFileName(desc->name()) + ".js"; |
| 2491 | descs_in_file[desc] = filename; |
| 2492 | } |
| 2493 | for (int j = 0; j < files[i]->enum_type_count(); j++) { |
| 2494 | const EnumDescriptor* desc = files[i]->enum_type(j); |
| 2495 | string filename = |
| 2496 | options.output_dir + "/" + ToFileName(desc->name()) + ".js"; |
| 2497 | descs_in_file[desc] = filename; |
| 2498 | } |
| 2499 | |
| 2500 | // For each (descriptor, filename) pair, update the |
| 2501 | // descriptors-by-filename map, and if a previous descriptor was already |
| 2502 | // writing the filename, remove it from the allowed-descriptors set. |
| 2503 | map<const void*, string>::iterator it; |
| 2504 | for (it = descs_in_file.begin(); it != descs_in_file.end(); ++it) { |
| 2505 | const void* desc = it->first; |
| 2506 | const string& filename = it->second; |
| 2507 | if (desc_by_filename.find(filename) != desc_by_filename.end()) { |
| 2508 | if (options.error_on_name_conflict) { |
| 2509 | *error = "Name conflict: file name " + filename + |
| 2510 | " would be generated by two descriptors"; |
| 2511 | return false; |
| 2512 | } |
| 2513 | allowed_descs.erase(desc_by_filename[filename]); |
| 2514 | } |
| 2515 | desc_by_filename[filename] = desc; |
| 2516 | allowed_descs.insert(desc); |
| 2517 | } |
| 2518 | } |
| 2519 | |
| 2520 | // Generate code. |
| 2521 | for (int i = 0; i < files.size(); i++) { |
| 2522 | const FileDescriptor* file = files[i]; |
| 2523 | for (int j = 0; j < file->message_type_count(); j++) { |
| 2524 | const Descriptor* desc = file->message_type(j); |
| 2525 | if (allowed_descs.find(desc) == allowed_descs.end()) { |
| 2526 | continue; |
| 2527 | } |
| 2528 | |
| 2529 | string filename = options.output_dir + "/" + |
| 2530 | ToFileName(desc->name()) + ".js"; |
| 2531 | google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output( |
| 2532 | context->Open(filename)); |
| 2533 | GOOGLE_CHECK(output.get()); |
| 2534 | io::Printer printer(output.get(), '$'); |
| 2535 | |
| 2536 | GenerateHeader(options, &printer); |
| 2537 | |
| 2538 | std::set<string> provided; |
| 2539 | FindProvidesForMessage(options, &printer, desc, &provided); |
| 2540 | GenerateProvides(options, &printer, &provided); |
| 2541 | GenerateTestOnly(options, &printer); |
| 2542 | GenerateRequires(options, &printer, desc, &provided); |
| 2543 | |
| 2544 | GenerateClass(options, &printer, desc); |
| 2545 | |
| 2546 | if (printer.failed()) { |
| 2547 | return false; |
| 2548 | } |
| 2549 | } |
| 2550 | for (int j = 0; j < file->enum_type_count(); j++) { |
| 2551 | const EnumDescriptor* enumdesc = file->enum_type(j); |
| 2552 | if (allowed_descs.find(enumdesc) == allowed_descs.end()) { |
| 2553 | continue; |
| 2554 | } |
| 2555 | |
| 2556 | string filename = options.output_dir + "/" + |
| 2557 | ToFileName(enumdesc->name()) + ".js"; |
| 2558 | |
| 2559 | google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output( |
| 2560 | context->Open(filename)); |
| 2561 | GOOGLE_CHECK(output.get()); |
| 2562 | io::Printer printer(output.get(), '$'); |
| 2563 | |
| 2564 | GenerateHeader(options, &printer); |
| 2565 | |
| 2566 | std::set<string> provided; |
| 2567 | FindProvidesForEnum(options, &printer, enumdesc, &provided); |
| 2568 | GenerateProvides(options, &printer, &provided); |
| 2569 | GenerateTestOnly(options, &printer); |
| 2570 | |
| 2571 | GenerateEnum(options, &printer, enumdesc); |
| 2572 | |
| 2573 | if (printer.failed()) { |
| 2574 | return false; |
| 2575 | } |
| 2576 | } |
| 2577 | // Pull out all free-floating extensions and generate files for those too. |
| 2578 | for (int j = 0; j < file->extension_count(); j++) { |
| 2579 | const FieldDescriptor* extension = file->extension(j); |
| 2580 | extensions_by_namespace[GetPath(options, files[i])] |
| 2581 | .push_back(extension); |
| 2582 | } |
| 2583 | } |
| 2584 | |
| 2585 | // Generate extensions in separate files. |
| 2586 | map< string, vector<const FieldDescriptor*> >::iterator it; |
| 2587 | for (it = extensions_by_namespace.begin(); |
| 2588 | it != extensions_by_namespace.end(); |
| 2589 | ++it) { |
| 2590 | string filename = options.output_dir + "/" + |
| 2591 | ToFileName(it->first) + ".js"; |
| 2592 | |
| 2593 | google::protobuf::scoped_ptr<io::ZeroCopyOutputStream> output( |
| 2594 | context->Open(filename)); |
| 2595 | GOOGLE_CHECK(output.get()); |
| 2596 | io::Printer printer(output.get(), '$'); |
| 2597 | |
| 2598 | GenerateHeader(options, &printer); |
| 2599 | |
| 2600 | std::set<string> provided; |
| 2601 | FindProvidesForFields(options, &printer, it->second, &provided); |
| 2602 | GenerateProvides(options, &printer, &provided); |
| 2603 | GenerateTestOnly(options, &printer); |
| 2604 | GenerateRequires(options, &printer, it->second, &provided); |
| 2605 | |
| 2606 | for (int j = 0; j < it->second.size(); j++) { |
| 2607 | if (ShouldGenerateExtension(it->second[j])) { |
| 2608 | GenerateExtension(options, &printer, it->second[j]); |
| 2609 | } |
| 2610 | } |
| 2611 | } |
| 2612 | } |
| 2613 | |
| 2614 | return true; |
| 2615 | } |
| 2616 | |
| 2617 | } // namespace js |
| 2618 | } // namespace compiler |
| 2619 | } // namespace protobuf |
| 2620 | } // namespace google |