OLD | NEW |
(Empty) | |
| 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/php/php_generator.h> |
| 32 |
| 33 #include <google/protobuf/compiler/code_generator.h> |
| 34 #include <google/protobuf/compiler/plugin.h> |
| 35 #include <google/protobuf/descriptor.h> |
| 36 #include <google/protobuf/descriptor.pb.h> |
| 37 #include <google/protobuf/io/printer.h> |
| 38 #include <google/protobuf/io/zero_copy_stream.h> |
| 39 #include <google/protobuf/stubs/strutil.h> |
| 40 |
| 41 #include <sstream> |
| 42 |
| 43 using google::protobuf::internal::scoped_ptr; |
| 44 |
| 45 const std::string kDescriptorFile = "google/protobuf/descriptor.proto"; |
| 46 const std::string kEmptyFile = "google/protobuf/empty.proto"; |
| 47 const std::string kEmptyMetadataFile = "GPBMetadata/Google/Protobuf/GPBEmpty.php
"; |
| 48 const std::string kDescriptorMetadataFile = |
| 49 "GPBMetadata/Google/Protobuf/Internal/Descriptor.php"; |
| 50 const std::string kDescriptorDirName = "Google/Protobuf/Internal"; |
| 51 const std::string kDescriptorPackageName = "Google\\Protobuf\\Internal"; |
| 52 |
| 53 namespace google { |
| 54 namespace protobuf { |
| 55 namespace compiler { |
| 56 namespace php { |
| 57 |
| 58 // Forward decls. |
| 59 std::string PhpName(const std::string& full_name, bool is_descriptor); |
| 60 std::string DefaultForField(FieldDescriptor* field); |
| 61 std::string IntToString(int32 value); |
| 62 std::string FilenameToClassname(const string& filename); |
| 63 std::string GeneratedMetadataFileName(const std::string& proto_file, |
| 64 bool is_descriptor); |
| 65 std::string LabelForField(FieldDescriptor* field); |
| 66 std::string TypeName(FieldDescriptor* field); |
| 67 std::string UnderscoresToCamelCase(const string& name, bool cap_first_letter); |
| 68 std::string EscapeDollor(const string& to_escape); |
| 69 std::string BinaryToHex(const string& binary); |
| 70 void Indent(io::Printer* printer); |
| 71 void Outdent(io::Printer* printer); |
| 72 void GenerateMessageDocComment(io::Printer* printer, const Descriptor* message); |
| 73 void GenerateFieldDocComment(io::Printer* printer, |
| 74 const FieldDescriptor* field); |
| 75 void GenerateEnumDocComment(io::Printer* printer, const EnumDescriptor* enum_); |
| 76 void GenerateEnumValueDocComment(io::Printer* printer, |
| 77 const EnumValueDescriptor* value); |
| 78 |
| 79 std::string RenameEmpty(const std::string& name) { |
| 80 if (name == "Empty") { |
| 81 return "GPBEmpty"; |
| 82 } else { |
| 83 return name; |
| 84 } |
| 85 } |
| 86 |
| 87 std::string MessagePrefix(const Descriptor* message) { |
| 88 // Empty cannot be php class name. |
| 89 if (message->name() == "Empty" && |
| 90 message->file()->package() == "google.protobuf") { |
| 91 return "GPB"; |
| 92 } else { |
| 93 return ""; |
| 94 } |
| 95 } |
| 96 |
| 97 std::string MessageName(const Descriptor* message, bool is_descriptor) { |
| 98 string message_name = message->name(); |
| 99 const Descriptor* descriptor = message->containing_type(); |
| 100 while (descriptor != NULL) { |
| 101 message_name = descriptor->name() + '_' + message_name; |
| 102 descriptor = descriptor->containing_type(); |
| 103 } |
| 104 message_name = MessagePrefix(message) + message_name; |
| 105 |
| 106 return PhpName(message->file()->package(), is_descriptor) + '\\' + |
| 107 message_name; |
| 108 } |
| 109 |
| 110 std::string MessageFullName(const Descriptor* message, bool is_descriptor) { |
| 111 if (is_descriptor) { |
| 112 return StringReplace(message->full_name(), |
| 113 "google.protobuf", |
| 114 "google.protobuf.internal", false); |
| 115 } else { |
| 116 return message->full_name(); |
| 117 } |
| 118 } |
| 119 |
| 120 std::string EnumFullName(const EnumDescriptor* envm, bool is_descriptor) { |
| 121 if (is_descriptor) { |
| 122 return StringReplace(envm->full_name(), |
| 123 "google.protobuf", |
| 124 "google.protobuf.internal", false); |
| 125 } else { |
| 126 return envm->full_name(); |
| 127 } |
| 128 } |
| 129 |
| 130 std::string EnumClassName(const EnumDescriptor* envm) { |
| 131 string enum_class_name = envm->name(); |
| 132 const Descriptor* descriptor = envm->containing_type(); |
| 133 while (descriptor != NULL) { |
| 134 enum_class_name = descriptor->name() + '_' + enum_class_name; |
| 135 descriptor = descriptor->containing_type(); |
| 136 } |
| 137 return enum_class_name; |
| 138 } |
| 139 |
| 140 std::string EnumName(const EnumDescriptor* envm, bool is_descriptor) { |
| 141 string enum_name = EnumClassName(envm); |
| 142 return PhpName(envm->file()->package(), is_descriptor) + '\\' + enum_name; |
| 143 } |
| 144 |
| 145 std::string PhpName(const std::string& full_name, bool is_descriptor) { |
| 146 if (is_descriptor) { |
| 147 return kDescriptorPackageName; |
| 148 } |
| 149 |
| 150 std::string result; |
| 151 bool cap_next_letter = true; |
| 152 for (int i = 0; i < full_name.size(); i++) { |
| 153 if ('a' <= full_name[i] && full_name[i] <= 'z' && cap_next_letter) { |
| 154 result += full_name[i] + ('A' - 'a'); |
| 155 cap_next_letter = false; |
| 156 } else if (full_name[i] == '.') { |
| 157 result += '\\'; |
| 158 cap_next_letter = true; |
| 159 } else { |
| 160 result += full_name[i]; |
| 161 cap_next_letter = false; |
| 162 } |
| 163 } |
| 164 return result; |
| 165 } |
| 166 |
| 167 std::string DefaultForField(const FieldDescriptor* field) { |
| 168 switch (field->type()) { |
| 169 case FieldDescriptor::TYPE_INT32: |
| 170 case FieldDescriptor::TYPE_INT64: |
| 171 case FieldDescriptor::TYPE_UINT32: |
| 172 case FieldDescriptor::TYPE_UINT64: |
| 173 case FieldDescriptor::TYPE_SINT32: |
| 174 case FieldDescriptor::TYPE_SINT64: |
| 175 case FieldDescriptor::TYPE_FIXED32: |
| 176 case FieldDescriptor::TYPE_FIXED64: |
| 177 case FieldDescriptor::TYPE_SFIXED32: |
| 178 case FieldDescriptor::TYPE_SFIXED64: |
| 179 case FieldDescriptor::TYPE_ENUM: return "0"; |
| 180 case FieldDescriptor::TYPE_DOUBLE: |
| 181 case FieldDescriptor::TYPE_FLOAT: return "0.0"; |
| 182 case FieldDescriptor::TYPE_BOOL: return "false"; |
| 183 case FieldDescriptor::TYPE_STRING: |
| 184 case FieldDescriptor::TYPE_BYTES: return "''"; |
| 185 case FieldDescriptor::TYPE_MESSAGE: |
| 186 case FieldDescriptor::TYPE_GROUP: return "null"; |
| 187 default: assert(false); return ""; |
| 188 } |
| 189 } |
| 190 |
| 191 std::string GeneratedMetadataFileName(const std::string& proto_file, |
| 192 bool is_descriptor) { |
| 193 int start_index = 0; |
| 194 int first_index = proto_file.find_first_of("/", start_index); |
| 195 std::string result = "GPBMetadata/"; |
| 196 |
| 197 if (proto_file == kEmptyFile) { |
| 198 return kEmptyMetadataFile; |
| 199 } |
| 200 if (is_descriptor) { |
| 201 return kDescriptorMetadataFile; |
| 202 } |
| 203 |
| 204 // Append directory name. |
| 205 std::string file_no_suffix; |
| 206 int lastindex = proto_file.find_last_of("."); |
| 207 if (proto_file == kEmptyFile) { |
| 208 return kEmptyMetadataFile; |
| 209 } else { |
| 210 file_no_suffix = proto_file.substr(0, lastindex); |
| 211 } |
| 212 |
| 213 while (first_index != string::npos) { |
| 214 result += UnderscoresToCamelCase( |
| 215 file_no_suffix.substr(start_index, first_index - start_index), true); |
| 216 result += "/"; |
| 217 start_index = first_index + 1; |
| 218 first_index = file_no_suffix.find_first_of("/", start_index); |
| 219 } |
| 220 |
| 221 // Append file name. |
| 222 result += RenameEmpty(UnderscoresToCamelCase( |
| 223 file_no_suffix.substr(start_index, first_index - start_index), true)); |
| 224 |
| 225 return result += ".php"; |
| 226 } |
| 227 |
| 228 std::string GeneratedMessageFileName(const Descriptor* message, |
| 229 bool is_descriptor) { |
| 230 std::string result = MessageName(message, is_descriptor); |
| 231 for (int i = 0; i < result.size(); i++) { |
| 232 if (result[i] == '\\') { |
| 233 result[i] = '/'; |
| 234 } |
| 235 } |
| 236 return result + ".php"; |
| 237 } |
| 238 |
| 239 std::string GeneratedEnumFileName(const EnumDescriptor* en, |
| 240 bool is_descriptor) { |
| 241 std::string result = EnumName(en, is_descriptor); |
| 242 for (int i = 0; i < result.size(); i++) { |
| 243 if (result[i] == '\\') { |
| 244 result[i] = '/'; |
| 245 } |
| 246 } |
| 247 return result + ".php"; |
| 248 } |
| 249 |
| 250 std::string IntToString(int32 value) { |
| 251 std::ostringstream os; |
| 252 os << value; |
| 253 return os.str(); |
| 254 } |
| 255 |
| 256 std::string LabelForField(const FieldDescriptor* field) { |
| 257 switch (field->label()) { |
| 258 case FieldDescriptor::LABEL_OPTIONAL: return "optional"; |
| 259 case FieldDescriptor::LABEL_REQUIRED: return "required"; |
| 260 case FieldDescriptor::LABEL_REPEATED: return "repeated"; |
| 261 default: assert(false); return ""; |
| 262 } |
| 263 } |
| 264 |
| 265 std::string TypeName(const FieldDescriptor* field) { |
| 266 switch (field->type()) { |
| 267 case FieldDescriptor::TYPE_INT32: return "int32"; |
| 268 case FieldDescriptor::TYPE_INT64: return "int64"; |
| 269 case FieldDescriptor::TYPE_UINT32: return "uint32"; |
| 270 case FieldDescriptor::TYPE_UINT64: return "uint64"; |
| 271 case FieldDescriptor::TYPE_SINT32: return "sint32"; |
| 272 case FieldDescriptor::TYPE_SINT64: return "sint64"; |
| 273 case FieldDescriptor::TYPE_FIXED32: return "fixed32"; |
| 274 case FieldDescriptor::TYPE_FIXED64: return "fixed64"; |
| 275 case FieldDescriptor::TYPE_SFIXED32: return "sfixed32"; |
| 276 case FieldDescriptor::TYPE_SFIXED64: return "sfixed64"; |
| 277 case FieldDescriptor::TYPE_DOUBLE: return "double"; |
| 278 case FieldDescriptor::TYPE_FLOAT: return "float"; |
| 279 case FieldDescriptor::TYPE_BOOL: return "bool"; |
| 280 case FieldDescriptor::TYPE_ENUM: return "enum"; |
| 281 case FieldDescriptor::TYPE_STRING: return "string"; |
| 282 case FieldDescriptor::TYPE_BYTES: return "bytes"; |
| 283 case FieldDescriptor::TYPE_MESSAGE: return "message"; |
| 284 case FieldDescriptor::TYPE_GROUP: return "group"; |
| 285 default: assert(false); return ""; |
| 286 } |
| 287 } |
| 288 |
| 289 std::string EnumOrMessageSuffix( |
| 290 const FieldDescriptor* field, bool is_descriptor) { |
| 291 if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { |
| 292 return ", '" + MessageFullName(field->message_type(), is_descriptor) + "'"; |
| 293 } |
| 294 if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) { |
| 295 return ", '" + EnumFullName(field->enum_type(), is_descriptor) + "'"; |
| 296 } |
| 297 return ""; |
| 298 } |
| 299 |
| 300 // Converts a name to camel-case. If cap_first_letter is true, capitalize the |
| 301 // first letter. |
| 302 std::string UnderscoresToCamelCase(const string& input, bool cap_first_letter) { |
| 303 std::string result; |
| 304 for (int i = 0; i < input.size(); i++) { |
| 305 if ('a' <= input[i] && input[i] <= 'z') { |
| 306 if (cap_first_letter) { |
| 307 result += input[i] + ('A' - 'a'); |
| 308 } else { |
| 309 result += input[i]; |
| 310 } |
| 311 cap_first_letter = false; |
| 312 } else if ('A' <= input[i] && input[i] <= 'Z') { |
| 313 if (i == 0 && !cap_first_letter) { |
| 314 // Force first letter to lower-case unless explicitly told to |
| 315 // capitalize it. |
| 316 result += input[i] + ('a' - 'A'); |
| 317 } else { |
| 318 // Capital letters after the first are left as-is. |
| 319 result += input[i]; |
| 320 } |
| 321 cap_first_letter = false; |
| 322 } else if ('0' <= input[i] && input[i] <= '9') { |
| 323 result += input[i]; |
| 324 cap_first_letter = true; |
| 325 } else { |
| 326 cap_first_letter = true; |
| 327 } |
| 328 } |
| 329 // Add a trailing "_" if the name should be altered. |
| 330 if (input[input.size() - 1] == '#') { |
| 331 result += '_'; |
| 332 } |
| 333 return result; |
| 334 } |
| 335 |
| 336 std::string EscapeDollor(const string& to_escape) { |
| 337 return StringReplace(to_escape, "$", "\\$", true); |
| 338 } |
| 339 |
| 340 std::string BinaryToHex(const string& src) { |
| 341 string dest; |
| 342 size_t i; |
| 343 unsigned char symbol[16] = { |
| 344 '0', '1', '2', '3', |
| 345 '4', '5', '6', '7', |
| 346 '8', '9', 'a', 'b', |
| 347 'c', 'd', 'e', 'f', |
| 348 }; |
| 349 |
| 350 dest.resize(src.size() * 2); |
| 351 char* append_ptr = &dest[0]; |
| 352 |
| 353 for (i = 0; i < src.size(); i++) { |
| 354 *append_ptr++ = symbol[(src[i] & 0xf0) >> 4]; |
| 355 *append_ptr++ = symbol[src[i] & 0x0f]; |
| 356 } |
| 357 |
| 358 return dest; |
| 359 } |
| 360 |
| 361 void Indent(io::Printer* printer) { |
| 362 printer->Indent(); |
| 363 printer->Indent(); |
| 364 } |
| 365 void Outdent(io::Printer* printer) { |
| 366 printer->Outdent(); |
| 367 printer->Outdent(); |
| 368 } |
| 369 |
| 370 void GenerateField(const FieldDescriptor* field, io::Printer* printer, |
| 371 bool is_descriptor) { |
| 372 if (field->is_repeated()) { |
| 373 GenerateFieldDocComment(printer, field); |
| 374 printer->Print( |
| 375 "private $^name^;\n", |
| 376 "name", field->name()); |
| 377 } else if (field->containing_oneof()) { |
| 378 // Oneof fields are handled by GenerateOneofField. |
| 379 return; |
| 380 } else { |
| 381 GenerateFieldDocComment(printer, field); |
| 382 printer->Print( |
| 383 "private $^name^ = ^default^;\n", |
| 384 "name", field->name(), |
| 385 "default", DefaultForField(field)); |
| 386 } |
| 387 |
| 388 if (is_descriptor) { |
| 389 printer->Print( |
| 390 "private $has_^name^ = false;\n", |
| 391 "name", field->name()); |
| 392 } |
| 393 } |
| 394 |
| 395 void GenerateOneofField(const OneofDescriptor* oneof, io::Printer* printer) { |
| 396 // Oneof property needs to be protected in order to be accessed by parent |
| 397 // class in implementation. |
| 398 printer->Print( |
| 399 "protected $^name^;\n", |
| 400 "name", oneof->name()); |
| 401 } |
| 402 |
| 403 void GenerateFieldAccessor(const FieldDescriptor* field, bool is_descriptor, |
| 404 io::Printer* printer) { |
| 405 const OneofDescriptor* oneof = field->containing_oneof(); |
| 406 |
| 407 // Generate getter. |
| 408 if (oneof != NULL) { |
| 409 GenerateFieldDocComment(printer, field); |
| 410 printer->Print( |
| 411 "public function get^camel_name^()\n" |
| 412 "{\n" |
| 413 " return $this->readOneof(^number^);\n" |
| 414 "}\n\n", |
| 415 "camel_name", UnderscoresToCamelCase(field->name(), true), |
| 416 "number", IntToString(field->number())); |
| 417 } else { |
| 418 GenerateFieldDocComment(printer, field); |
| 419 printer->Print( |
| 420 "public function get^camel_name^()\n" |
| 421 "{\n" |
| 422 " return $this->^name^;\n" |
| 423 "}\n\n", |
| 424 "camel_name", UnderscoresToCamelCase(field->name(), true), "name", |
| 425 field->name()); |
| 426 } |
| 427 |
| 428 // Generate setter. |
| 429 GenerateFieldDocComment(printer, field); |
| 430 printer->Print( |
| 431 "public function set^camel_name^(^var^)\n" |
| 432 "{\n", |
| 433 "camel_name", UnderscoresToCamelCase(field->name(), true), |
| 434 "var", (field->is_repeated() || |
| 435 field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ? |
| 436 "&$var": "$var"); |
| 437 |
| 438 Indent(printer); |
| 439 |
| 440 // Type check. |
| 441 if (field->is_map()) { |
| 442 } else if (field->is_repeated()) { |
| 443 printer->Print( |
| 444 "GPBUtil::checkRepeatedField($var, " |
| 445 "\\Google\\Protobuf\\Internal\\GPBType::^type^", |
| 446 "type", ToUpper(field->type_name())); |
| 447 if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { |
| 448 printer->Print( |
| 449 ", \\^class_name^);\n", |
| 450 "class_name", |
| 451 MessageName(field->message_type(), is_descriptor) + "::class"); |
| 452 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) { |
| 453 printer->Print( |
| 454 ", ^class_name^);\n", |
| 455 "class_name", |
| 456 EnumName(field->enum_type(), is_descriptor) + "::class"); |
| 457 } else { |
| 458 printer->Print(");\n"); |
| 459 } |
| 460 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { |
| 461 printer->Print( |
| 462 "GPBUtil::checkMessage($var, \\^class_name^::class);\n", |
| 463 "class_name", MessageName(field->message_type(), is_descriptor)); |
| 464 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) { |
| 465 printer->Print( |
| 466 "GPBUtil::checkEnum($var, \\^class_name^::class);\n", |
| 467 "class_name", EnumName(field->enum_type(), is_descriptor)); |
| 468 } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_STRING) { |
| 469 printer->Print( |
| 470 "GPBUtil::checkString($var, ^utf8^);\n", |
| 471 "utf8", |
| 472 field->type() == FieldDescriptor::TYPE_STRING ? "True": "False"); |
| 473 } else { |
| 474 printer->Print( |
| 475 "GPBUtil::check^type^($var);\n", |
| 476 "type", UnderscoresToCamelCase(field->cpp_type_name(), true)); |
| 477 } |
| 478 |
| 479 if (oneof != NULL) { |
| 480 printer->Print( |
| 481 "$this->writeOneof(^number^, $var);\n", |
| 482 "number", IntToString(field->number())); |
| 483 } else { |
| 484 printer->Print( |
| 485 "$this->^name^ = $var;\n", |
| 486 "name", field->name()); |
| 487 } |
| 488 |
| 489 // Set has bit for proto2 only. |
| 490 if (is_descriptor) { |
| 491 printer->Print( |
| 492 "$this->has_^field_name^ = true;\n", |
| 493 "field_name", field->name()); |
| 494 } |
| 495 |
| 496 Outdent(printer); |
| 497 |
| 498 printer->Print( |
| 499 "}\n\n"); |
| 500 |
| 501 // Generate has method for proto2 only. |
| 502 if (is_descriptor) { |
| 503 printer->Print( |
| 504 "public function has^camel_name^()\n" |
| 505 "{\n" |
| 506 " return $this->has_^field_name^;\n" |
| 507 "}\n\n", |
| 508 "camel_name", UnderscoresToCamelCase(field->name(), true), |
| 509 "field_name", field->name()); |
| 510 } |
| 511 } |
| 512 |
| 513 void GenerateEnumToPool(const EnumDescriptor* en, io::Printer* printer) { |
| 514 printer->Print( |
| 515 "$pool->addEnum('^name^', " |
| 516 "\\Google\\Protobuf\\Internal\\^class_name^::class)\n", |
| 517 "name", EnumFullName(en, true), |
| 518 "class_name", en->name()); |
| 519 Indent(printer); |
| 520 |
| 521 for (int i = 0; i < en->value_count(); i++) { |
| 522 const EnumValueDescriptor* value = en->value(i); |
| 523 printer->Print( |
| 524 "->value(\"^name^\", ^number^)\n", |
| 525 "name", value->name(), |
| 526 "number", IntToString(value->number())); |
| 527 } |
| 528 printer->Print("->finalizeToPool();\n\n"); |
| 529 Outdent(printer); |
| 530 } |
| 531 |
| 532 void GenerateMessageToPool(const string& name_prefix, const Descriptor* message, |
| 533 io::Printer* printer) { |
| 534 // Don't generate MapEntry messages -- we use the PHP extension's native |
| 535 // support for map fields instead. |
| 536 if (message->options().map_entry()) { |
| 537 return; |
| 538 } |
| 539 string class_name = name_prefix.empty()? |
| 540 message->name() : name_prefix + "_" + message->name(); |
| 541 |
| 542 printer->Print( |
| 543 "$pool->addMessage('^message^', " |
| 544 "\\Google\\Protobuf\\Internal\\^class_name^::class)\n", |
| 545 "message", MessageFullName(message, true), |
| 546 "class_name", class_name); |
| 547 |
| 548 Indent(printer); |
| 549 |
| 550 for (int i = 0; i < message->field_count(); i++) { |
| 551 const FieldDescriptor* field = message->field(i); |
| 552 if (field->is_map()) { |
| 553 const FieldDescriptor* key = |
| 554 field->message_type()->FindFieldByName("key"); |
| 555 const FieldDescriptor* val = |
| 556 field->message_type()->FindFieldByName("value"); |
| 557 printer->Print( |
| 558 "->map('^field^', \\Google\\Protobuf\\Internal\\GPBType::^key^, " |
| 559 "\\Google\\Protobuf\\Internal\\GPBType::^value^, ^number^^other^)\n", |
| 560 "field", field->name(), |
| 561 "key", ToUpper(key->type_name()), |
| 562 "value", ToUpper(val->type_name()), |
| 563 "number", SimpleItoa(field->number()), |
| 564 "other", EnumOrMessageSuffix(val, true)); |
| 565 } else if (!field->containing_oneof()) { |
| 566 printer->Print( |
| 567 "->^label^('^field^', " |
| 568 "\\Google\\Protobuf\\Internal\\GPBType::^type^, ^number^^other^)\n", |
| 569 "field", field->name(), |
| 570 "label", LabelForField(field), |
| 571 "type", ToUpper(field->type_name()), |
| 572 "number", SimpleItoa(field->number()), |
| 573 "other", EnumOrMessageSuffix(field, true)); |
| 574 } |
| 575 } |
| 576 |
| 577 // oneofs. |
| 578 for (int i = 0; i < message->oneof_decl_count(); i++) { |
| 579 const OneofDescriptor* oneof = message->oneof_decl(i); |
| 580 printer->Print("->oneof(^name^)\n", |
| 581 "name", oneof->name()); |
| 582 Indent(printer); |
| 583 for (int index = 0; index < oneof->field_count(); index++) { |
| 584 const FieldDescriptor* field = oneof->field(index); |
| 585 printer->Print( |
| 586 "->value('^field^', " |
| 587 "\\Google\\Protobuf\\Internal\\GPBType::^type^, ^number^^other^)\n", |
| 588 "field", field->name(), |
| 589 "type", ToUpper(field->type_name()), |
| 590 "number", SimpleItoa(field->number()), |
| 591 "other", EnumOrMessageSuffix(field, true)); |
| 592 } |
| 593 printer->Print("->finish()\n"); |
| 594 Outdent(printer); |
| 595 } |
| 596 |
| 597 printer->Print( |
| 598 "->finalizeToPool();\n"); |
| 599 |
| 600 Outdent(printer); |
| 601 |
| 602 printer->Print( |
| 603 "\n"); |
| 604 |
| 605 for (int i = 0; i < message->nested_type_count(); i++) { |
| 606 GenerateMessageToPool(class_name, message->nested_type(i), printer); |
| 607 } |
| 608 for (int i = 0; i < message->enum_type_count(); i++) { |
| 609 GenerateEnumToPool(message->enum_type(i), printer); |
| 610 } |
| 611 } |
| 612 |
| 613 void GenerateAddFileToPool(const FileDescriptor* file, bool is_descriptor, |
| 614 io::Printer* printer) { |
| 615 printer->Print( |
| 616 "public static $is_initialized = false;\n\n" |
| 617 "public static function initOnce() {\n"); |
| 618 Indent(printer); |
| 619 |
| 620 printer->Print( |
| 621 "$pool = \\Google\\Protobuf\\Internal\\" |
| 622 "DescriptorPool::getGeneratedPool();\n\n" |
| 623 "if (static::$is_initialized == true) {\n" |
| 624 " return;\n" |
| 625 "}\n"); |
| 626 |
| 627 if (is_descriptor) { |
| 628 for (int i = 0; i < file->message_type_count(); i++) { |
| 629 GenerateMessageToPool("", file->message_type(i), printer); |
| 630 } |
| 631 for (int i = 0; i < file->enum_type_count(); i++) { |
| 632 GenerateEnumToPool(file->enum_type(i), printer); |
| 633 } |
| 634 |
| 635 printer->Print( |
| 636 "$pool->finish();\n"); |
| 637 } else { |
| 638 for (int i = 0; i < file->dependency_count(); i++) { |
| 639 const std::string& name = file->dependency(i)->name(); |
| 640 std::string dependency_filename = |
| 641 GeneratedMetadataFileName(name, is_descriptor); |
| 642 printer->Print( |
| 643 "\\^name^::initOnce();\n", |
| 644 "name", FilenameToClassname(dependency_filename)); |
| 645 } |
| 646 |
| 647 // Add messages and enums to descriptor pool. |
| 648 FileDescriptorSet files; |
| 649 FileDescriptorProto* file_proto = files.add_file(); |
| 650 file->CopyTo(file_proto); |
| 651 string files_data; |
| 652 files.SerializeToString(&files_data); |
| 653 |
| 654 printer->Print("$pool->internalAddGeneratedFile(hex2bin(\n"); |
| 655 Indent(printer); |
| 656 |
| 657 // Only write 30 bytes per line. |
| 658 static const int kBytesPerLine = 30; |
| 659 for (int i = 0; i < files_data.size(); i += kBytesPerLine) { |
| 660 printer->Print( |
| 661 "\"^data^\"^dot^\n", |
| 662 "data", BinaryToHex(files_data.substr(i, kBytesPerLine)), |
| 663 "dot", i + kBytesPerLine < files_data.size() ? " ." : ""); |
| 664 } |
| 665 |
| 666 Outdent(printer); |
| 667 printer->Print( |
| 668 "));\n\n"); |
| 669 } |
| 670 printer->Print( |
| 671 "static::$is_initialized = true;\n"); |
| 672 Outdent(printer); |
| 673 printer->Print("}\n"); |
| 674 } |
| 675 |
| 676 void GenerateUseDeclaration(bool is_descriptor, io::Printer* printer) { |
| 677 if (!is_descriptor) { |
| 678 printer->Print( |
| 679 "use Google\\Protobuf\\Internal\\GPBType;\n" |
| 680 "use Google\\Protobuf\\Internal\\RepeatedField;\n" |
| 681 "use Google\\Protobuf\\Internal\\GPBUtil;\n\n"); |
| 682 } else { |
| 683 printer->Print( |
| 684 "use Google\\Protobuf\\Internal\\GPBType;\n" |
| 685 "use Google\\Protobuf\\Internal\\GPBWire;\n" |
| 686 "use Google\\Protobuf\\Internal\\RepeatedField;\n" |
| 687 "use Google\\Protobuf\\Internal\\InputStream;\n\n" |
| 688 "use Google\\Protobuf\\Internal\\GPBUtil;\n\n"); |
| 689 } |
| 690 } |
| 691 |
| 692 void GenerateHead(const FileDescriptor* file, io::Printer* printer) { |
| 693 printer->Print( |
| 694 "<?php\n" |
| 695 "# Generated by the protocol buffer compiler. DO NOT EDIT!\n" |
| 696 "# source: ^filename^\n" |
| 697 "\n", |
| 698 "filename", file->name()); |
| 699 } |
| 700 |
| 701 std::string FilenameToClassname(const string& filename) { |
| 702 int lastindex = filename.find_last_of("."); |
| 703 std::string result = filename.substr(0, lastindex); |
| 704 for (int i = 0; i < result.size(); i++) { |
| 705 if (result[i] == '/') { |
| 706 result[i] = '\\'; |
| 707 } |
| 708 } |
| 709 return result; |
| 710 } |
| 711 |
| 712 void GenerateMetadataFile(const FileDescriptor* file, |
| 713 bool is_descriptor, |
| 714 GeneratorContext* generator_context) { |
| 715 std::string filename = GeneratedMetadataFileName(file->name(), is_descriptor); |
| 716 scoped_ptr<io::ZeroCopyOutputStream> output( |
| 717 generator_context->Open(filename)); |
| 718 io::Printer printer(output.get(), '^'); |
| 719 |
| 720 GenerateHead(file, &printer); |
| 721 |
| 722 std::string fullname = FilenameToClassname(filename); |
| 723 int lastindex = fullname.find_last_of("\\"); |
| 724 |
| 725 printer.Print( |
| 726 "namespace ^name^;\n\n", |
| 727 "name", fullname.substr(0, lastindex)); |
| 728 |
| 729 if (lastindex != string::npos) { |
| 730 printer.Print( |
| 731 "class ^name^\n" |
| 732 "{\n", |
| 733 "name", fullname.substr(lastindex + 1)); |
| 734 } else { |
| 735 printer.Print( |
| 736 "class ^name^\n" |
| 737 "{\n", |
| 738 "name", fullname); |
| 739 } |
| 740 Indent(&printer); |
| 741 |
| 742 GenerateAddFileToPool(file, is_descriptor, &printer); |
| 743 |
| 744 Outdent(&printer); |
| 745 printer.Print("}\n\n"); |
| 746 } |
| 747 |
| 748 void GenerateEnumFile(const FileDescriptor* file, const EnumDescriptor* en, |
| 749 bool is_descriptor, GeneratorContext* generator_context) { |
| 750 std::string filename = GeneratedEnumFileName(en, is_descriptor); |
| 751 scoped_ptr<io::ZeroCopyOutputStream> output( |
| 752 generator_context->Open(filename)); |
| 753 io::Printer printer(output.get(), '^'); |
| 754 |
| 755 GenerateHead(file, &printer); |
| 756 |
| 757 std::string fullname = FilenameToClassname(filename); |
| 758 int lastindex = fullname.find_last_of("\\"); |
| 759 |
| 760 GenerateEnumDocComment(&printer, en); |
| 761 if (lastindex != string::npos) { |
| 762 printer.Print( |
| 763 "namespace ^name^;\n\n", |
| 764 "name", fullname.substr(0, lastindex)); |
| 765 |
| 766 printer.Print( |
| 767 "class ^name^\n" |
| 768 "{\n", |
| 769 "name", fullname.substr(lastindex + 1)); |
| 770 } else { |
| 771 printer.Print( |
| 772 "class ^name^\n" |
| 773 "{\n", |
| 774 "name", fullname); |
| 775 } |
| 776 Indent(&printer); |
| 777 |
| 778 for (int i = 0; i < en->value_count(); i++) { |
| 779 const EnumValueDescriptor* value = en->value(i); |
| 780 GenerateEnumValueDocComment(&printer, value); |
| 781 printer.Print("const ^name^ = ^number^;\n", |
| 782 "name", value->name(), |
| 783 "number", IntToString(value->number())); |
| 784 } |
| 785 |
| 786 Outdent(&printer); |
| 787 printer.Print("}\n\n"); |
| 788 } |
| 789 |
| 790 void GenerateMessageFile(const FileDescriptor* file, const Descriptor* message, |
| 791 bool is_descriptor, |
| 792 GeneratorContext* generator_context) { |
| 793 // Don't generate MapEntry messages -- we use the PHP extension's native |
| 794 // support for map fields instead. |
| 795 if (message->options().map_entry()) { |
| 796 return; |
| 797 } |
| 798 |
| 799 std::string filename = GeneratedMessageFileName(message, is_descriptor); |
| 800 scoped_ptr<io::ZeroCopyOutputStream> output( |
| 801 generator_context->Open(filename)); |
| 802 io::Printer printer(output.get(), '^'); |
| 803 |
| 804 GenerateHead(file, &printer); |
| 805 |
| 806 std::string fullname = FilenameToClassname(filename); |
| 807 int lastindex = fullname.find_last_of("\\"); |
| 808 |
| 809 if (!file->package().empty()) { |
| 810 printer.Print( |
| 811 "namespace ^name^;\n\n", |
| 812 "name", fullname.substr(0, lastindex)); |
| 813 } |
| 814 |
| 815 GenerateUseDeclaration(is_descriptor, &printer); |
| 816 |
| 817 GenerateMessageDocComment(&printer, message); |
| 818 if (lastindex != string::npos) { |
| 819 printer.Print( |
| 820 "class ^name^ extends \\Google\\Protobuf\\Internal\\Message\n" |
| 821 "{\n", |
| 822 "name", fullname.substr(lastindex + 1)); |
| 823 } else { |
| 824 printer.Print( |
| 825 "class ^name^ extends \\Google\\Protobuf\\Internal\\Message\n" |
| 826 "{\n", |
| 827 "name", fullname); |
| 828 } |
| 829 Indent(&printer); |
| 830 |
| 831 // Field and oneof definitions. |
| 832 for (int i = 0; i < message->field_count(); i++) { |
| 833 const FieldDescriptor* field = message->field(i); |
| 834 GenerateField(field, &printer, is_descriptor); |
| 835 } |
| 836 for (int i = 0; i < message->oneof_decl_count(); i++) { |
| 837 const OneofDescriptor* oneof = message->oneof_decl(i); |
| 838 GenerateOneofField(oneof, &printer); |
| 839 } |
| 840 printer.Print("\n"); |
| 841 |
| 842 printer.Print( |
| 843 "public function __construct() {\n"); |
| 844 Indent(&printer); |
| 845 |
| 846 std::string metadata_filename = |
| 847 GeneratedMetadataFileName(file->name(), is_descriptor); |
| 848 std::string metadata_fullname = FilenameToClassname(metadata_filename); |
| 849 printer.Print( |
| 850 "\\^fullname^::initOnce();\n" |
| 851 "parent::__construct();\n", |
| 852 "fullname", metadata_fullname); |
| 853 |
| 854 Outdent(&printer); |
| 855 printer.Print("}\n\n"); |
| 856 |
| 857 // Field and oneof accessors. |
| 858 for (int i = 0; i < message->field_count(); i++) { |
| 859 const FieldDescriptor* field = message->field(i); |
| 860 GenerateFieldAccessor(field, is_descriptor, &printer); |
| 861 } |
| 862 for (int i = 0; i < message->oneof_decl_count(); i++) { |
| 863 const OneofDescriptor* oneof = message->oneof_decl(i); |
| 864 printer.Print( |
| 865 "public function get^camel_name^()\n" |
| 866 "{\n" |
| 867 " return $this->^name^;\n" |
| 868 "}\n\n", |
| 869 "camel_name", UnderscoresToCamelCase(oneof->name(), true), "name", |
| 870 oneof->name()); |
| 871 } |
| 872 |
| 873 Outdent(&printer); |
| 874 printer.Print("}\n\n"); |
| 875 |
| 876 // Nested messages and enums. |
| 877 for (int i = 0; i < message->nested_type_count(); i++) { |
| 878 GenerateMessageFile(file, message->nested_type(i), is_descriptor, |
| 879 generator_context); |
| 880 } |
| 881 for (int i = 0; i < message->enum_type_count(); i++) { |
| 882 GenerateEnumFile(file, message->enum_type(i), is_descriptor, |
| 883 generator_context); |
| 884 } |
| 885 } |
| 886 |
| 887 void GenerateFile(const FileDescriptor* file, bool is_descriptor, |
| 888 GeneratorContext* generator_context) { |
| 889 GenerateMetadataFile(file, is_descriptor, generator_context); |
| 890 for (int i = 0; i < file->message_type_count(); i++) { |
| 891 GenerateMessageFile(file, file->message_type(i), is_descriptor, |
| 892 generator_context); |
| 893 } |
| 894 for (int i = 0; i < file->enum_type_count(); i++) { |
| 895 GenerateEnumFile(file, file->enum_type(i), is_descriptor, |
| 896 generator_context); |
| 897 } |
| 898 } |
| 899 |
| 900 static string EscapePhpdoc(const string& input) { |
| 901 string result; |
| 902 result.reserve(input.size() * 2); |
| 903 |
| 904 char prev = '*'; |
| 905 |
| 906 for (string::size_type i = 0; i < input.size(); i++) { |
| 907 char c = input[i]; |
| 908 switch (c) { |
| 909 case '*': |
| 910 // Avoid "/*". |
| 911 if (prev == '/') { |
| 912 result.append("*"); |
| 913 } else { |
| 914 result.push_back(c); |
| 915 } |
| 916 break; |
| 917 case '/': |
| 918 // Avoid "*/". |
| 919 if (prev == '*') { |
| 920 result.append("/"); |
| 921 } else { |
| 922 result.push_back(c); |
| 923 } |
| 924 break; |
| 925 case '@': |
| 926 // '@' starts phpdoc tags including the @deprecated tag, which will |
| 927 // cause a compile-time error if inserted before a declaration that |
| 928 // does not have a corresponding @Deprecated annotation. |
| 929 result.append("@"); |
| 930 break; |
| 931 case '<': |
| 932 // Avoid interpretation as HTML. |
| 933 result.append("<"); |
| 934 break; |
| 935 case '>': |
| 936 // Avoid interpretation as HTML. |
| 937 result.append(">"); |
| 938 break; |
| 939 case '&': |
| 940 // Avoid interpretation as HTML. |
| 941 result.append("&"); |
| 942 break; |
| 943 case '\\': |
| 944 // Java interprets Unicode escape sequences anywhere! |
| 945 result.append("\"); |
| 946 break; |
| 947 default: |
| 948 result.push_back(c); |
| 949 break; |
| 950 } |
| 951 |
| 952 prev = c; |
| 953 } |
| 954 |
| 955 return result; |
| 956 } |
| 957 |
| 958 static void GenerateDocCommentBodyForLocation( |
| 959 io::Printer* printer, const SourceLocation& location) { |
| 960 string comments = location.leading_comments.empty() ? |
| 961 location.trailing_comments : location.leading_comments; |
| 962 if (!comments.empty()) { |
| 963 // TODO(teboring): Ideally we should parse the comment text as Markdown and |
| 964 // write it back as HTML, but this requires a Markdown parser. For now |
| 965 // we just use <pre> to get fixed-width text formatting. |
| 966 |
| 967 // If the comment itself contains block comment start or end markers, |
| 968 // HTML-escape them so that they don't accidentally close the doc comment. |
| 969 comments = EscapePhpdoc(comments); |
| 970 |
| 971 vector<string> lines = Split(comments, "\n"); |
| 972 while (!lines.empty() && lines.back().empty()) { |
| 973 lines.pop_back(); |
| 974 } |
| 975 |
| 976 printer->Print(" * <pre>\n"); |
| 977 for (int i = 0; i < lines.size(); i++) { |
| 978 // Most lines should start with a space. Watch out for lines that start |
| 979 // with a /, since putting that right after the leading asterisk will |
| 980 // close the comment. |
| 981 if (!lines[i].empty() && lines[i][0] == '/') { |
| 982 printer->Print(" * ^line^\n", "line", lines[i]); |
| 983 } else { |
| 984 printer->Print(" *^line^\n", "line", lines[i]); |
| 985 } |
| 986 } |
| 987 printer->Print( |
| 988 " * </pre>\n" |
| 989 " *\n"); |
| 990 } |
| 991 } |
| 992 |
| 993 template <typename DescriptorType> |
| 994 static void GenerateDocCommentBody( |
| 995 io::Printer* printer, const DescriptorType* descriptor) { |
| 996 SourceLocation location; |
| 997 if (descriptor->GetSourceLocation(&location)) { |
| 998 GenerateDocCommentBodyForLocation(printer, location); |
| 999 } |
| 1000 } |
| 1001 |
| 1002 static string FirstLineOf(const string& value) { |
| 1003 string result = value; |
| 1004 |
| 1005 string::size_type pos = result.find_first_of('\n'); |
| 1006 if (pos != string::npos) { |
| 1007 result.erase(pos); |
| 1008 } |
| 1009 |
| 1010 return result; |
| 1011 } |
| 1012 |
| 1013 void GenerateMessageDocComment(io::Printer* printer, |
| 1014 const Descriptor* message) { |
| 1015 printer->Print("/**\n"); |
| 1016 GenerateDocCommentBody(printer, message); |
| 1017 printer->Print( |
| 1018 " * Protobuf type <code>^fullname^</code>\n" |
| 1019 " */\n", |
| 1020 "fullname", EscapePhpdoc(message->full_name())); |
| 1021 } |
| 1022 |
| 1023 void GenerateFieldDocComment(io::Printer* printer, |
| 1024 const FieldDescriptor* field) { |
| 1025 // In theory we should have slightly different comments for setters, getters, |
| 1026 // etc., but in practice everyone already knows the difference between these |
| 1027 // so it's redundant information. |
| 1028 |
| 1029 // We start the comment with the main body based on the comments from the |
| 1030 // .proto file (if present). We then end with the field declaration, e.g.: |
| 1031 // optional string foo = 5; |
| 1032 // If the field is a group, the debug string might end with {. |
| 1033 printer->Print("/**\n"); |
| 1034 GenerateDocCommentBody(printer, field); |
| 1035 printer->Print( |
| 1036 " * <code>^def^</code>\n", |
| 1037 "def", EscapePhpdoc(FirstLineOf(field->DebugString()))); |
| 1038 printer->Print(" */\n"); |
| 1039 } |
| 1040 |
| 1041 void GenerateEnumDocComment(io::Printer* printer, const EnumDescriptor* enum_) { |
| 1042 printer->Print("/**\n"); |
| 1043 GenerateDocCommentBody(printer, enum_); |
| 1044 printer->Print( |
| 1045 " * Protobuf enum <code>^fullname^</code>\n" |
| 1046 " */\n", |
| 1047 "fullname", EscapePhpdoc(enum_->full_name())); |
| 1048 } |
| 1049 |
| 1050 void GenerateEnumValueDocComment(io::Printer* printer, |
| 1051 const EnumValueDescriptor* value) { |
| 1052 printer->Print("/**\n"); |
| 1053 GenerateDocCommentBody(printer, value); |
| 1054 printer->Print( |
| 1055 " * <code>^def^</code>\n" |
| 1056 " */\n", |
| 1057 "def", EscapePhpdoc(FirstLineOf(value->DebugString()))); |
| 1058 } |
| 1059 |
| 1060 bool Generator::Generate(const FileDescriptor* file, const string& parameter, |
| 1061 GeneratorContext* generator_context, |
| 1062 string* error) const { |
| 1063 bool is_descriptor = parameter == "internal"; |
| 1064 |
| 1065 if (is_descriptor && file->name() != kDescriptorFile) { |
| 1066 *error = |
| 1067 "Can only generate PHP code for google/protobuf/descriptor.proto.\n"; |
| 1068 return false; |
| 1069 } |
| 1070 |
| 1071 if (!is_descriptor && file->syntax() != FileDescriptor::SYNTAX_PROTO3) { |
| 1072 *error = |
| 1073 "Can only generate PHP code for proto3 .proto files.\n" |
| 1074 "Please add 'syntax = \"proto3\";' to the top of your .proto file.\n"; |
| 1075 return false; |
| 1076 } |
| 1077 |
| 1078 GenerateFile(file, is_descriptor, generator_context); |
| 1079 |
| 1080 return true; |
| 1081 } |
| 1082 |
| 1083 } // namespace php |
| 1084 } // namespace compiler |
| 1085 } // namespace protobuf |
| 1086 } // namespace google |
OLD | NEW |