| 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 | // Author: kenton@google.com (Kenton Varda) |
| 32 | // Based on original Protocol Buffers design by |
| 33 | // Sanjay Ghemawat, Jeff Dean, and others. |
| 34 | |
| 35 | #include <google/protobuf/compiler/java/doc_comment.h> |
| 36 | |
| 37 | #include <vector> |
| 38 | |
| 39 | #include <google/protobuf/io/printer.h> |
| 40 | #include <google/protobuf/stubs/strutil.h> |
| 41 | #include <google/protobuf/descriptor.pb.h> |
| 42 | |
| 43 | namespace google { |
| 44 | namespace protobuf { |
| 45 | namespace compiler { |
| 46 | namespace java { |
| 47 | |
| 48 | std::string EscapeJavadoc(const std::string& input) { |
| 49 | std::string result; |
| 50 | result.reserve(res_arg: input.size() * 2); |
| 51 | |
| 52 | char prev = '*'; |
| 53 | |
| 54 | for (std::string::size_type i = 0; i < input.size(); i++) { |
| 55 | char c = input[i]; |
| 56 | switch (c) { |
| 57 | case '*': |
| 58 | // Avoid "/*". |
| 59 | if (prev == '/') { |
| 60 | result.append(s: "*" ); |
| 61 | } else { |
| 62 | result.push_back(c: c); |
| 63 | } |
| 64 | break; |
| 65 | case '/': |
| 66 | // Avoid "*/". |
| 67 | if (prev == '*') { |
| 68 | result.append(s: "/" ); |
| 69 | } else { |
| 70 | result.push_back(c: c); |
| 71 | } |
| 72 | break; |
| 73 | case '@': |
| 74 | // '@' starts javadoc tags including the @deprecated tag, which will |
| 75 | // cause a compile-time error if inserted before a declaration that |
| 76 | // does not have a corresponding @Deprecated annotation. |
| 77 | result.append(s: "@" ); |
| 78 | break; |
| 79 | case '<': |
| 80 | // Avoid interpretation as HTML. |
| 81 | result.append(s: "<" ); |
| 82 | break; |
| 83 | case '>': |
| 84 | // Avoid interpretation as HTML. |
| 85 | result.append(s: ">" ); |
| 86 | break; |
| 87 | case '&': |
| 88 | // Avoid interpretation as HTML. |
| 89 | result.append(s: "&" ); |
| 90 | break; |
| 91 | case '\\': |
| 92 | // Java interprets Unicode escape sequences anywhere! |
| 93 | result.append(s: "\" ); |
| 94 | break; |
| 95 | default: |
| 96 | result.push_back(c: c); |
| 97 | break; |
| 98 | } |
| 99 | |
| 100 | prev = c; |
| 101 | } |
| 102 | |
| 103 | return result; |
| 104 | } |
| 105 | |
| 106 | static void WriteDocCommentBodyForLocation(io::Printer* printer, |
| 107 | const SourceLocation& location) { |
| 108 | std::string = location.leading_comments.empty() |
| 109 | ? location.trailing_comments |
| 110 | : location.leading_comments; |
| 111 | if (!comments.empty()) { |
| 112 | // TODO(kenton): Ideally we should parse the comment text as Markdown and |
| 113 | // write it back as HTML, but this requires a Markdown parser. For now |
| 114 | // we just use <pre> to get fixed-width text formatting. |
| 115 | |
| 116 | // If the comment itself contains block comment start or end markers, |
| 117 | // HTML-escape them so that they don't accidentally close the doc comment. |
| 118 | comments = EscapeJavadoc(input: comments); |
| 119 | |
| 120 | std::vector<std::string> lines = Split(full: comments, delim: "\n" ); |
| 121 | while (!lines.empty() && lines.back().empty()) { |
| 122 | lines.pop_back(); |
| 123 | } |
| 124 | |
| 125 | printer->Print(text: " * <pre>\n" ); |
| 126 | for (int i = 0; i < lines.size(); i++) { |
| 127 | // Most lines should start with a space. Watch out for lines that start |
| 128 | // with a /, since putting that right after the leading asterisk will |
| 129 | // close the comment. |
| 130 | if (!lines[i].empty() && lines[i][0] == '/') { |
| 131 | printer->Print(text: " * $line$\n" , args: "line" , args: lines[i]); |
| 132 | } else { |
| 133 | printer->Print(text: " *$line$\n" , args: "line" , args: lines[i]); |
| 134 | } |
| 135 | } |
| 136 | printer->Print( |
| 137 | text: " * </pre>\n" |
| 138 | " *\n" ); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | template <typename DescriptorType> |
| 143 | static void WriteDocCommentBody(io::Printer* printer, |
| 144 | const DescriptorType* descriptor) { |
| 145 | SourceLocation location; |
| 146 | if (descriptor->GetSourceLocation(&location)) { |
| 147 | WriteDocCommentBodyForLocation(printer, location); |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | static std::string FirstLineOf(const std::string& value) { |
| 152 | std::string result = value; |
| 153 | |
| 154 | std::string::size_type pos = result.find_first_of(c: '\n'); |
| 155 | if (pos != std::string::npos) { |
| 156 | result.erase(pos: pos); |
| 157 | } |
| 158 | |
| 159 | // If line ends in an opening brace, make it "{ ... }" so it looks nice. |
| 160 | if (!result.empty() && result[result.size() - 1] == '{') { |
| 161 | result.append(s: " ... }" ); |
| 162 | } |
| 163 | |
| 164 | return result; |
| 165 | } |
| 166 | |
| 167 | void (io::Printer* printer, const Descriptor* message) { |
| 168 | printer->Print(text: "/**\n" ); |
| 169 | WriteDocCommentBody(printer, descriptor: message); |
| 170 | printer->Print( |
| 171 | text: " * Protobuf type {@code $fullname$}\n" |
| 172 | " */\n" , |
| 173 | args: "fullname" , args: EscapeJavadoc(input: message->full_name())); |
| 174 | } |
| 175 | |
| 176 | void (io::Printer* printer, const FieldDescriptor* field) { |
| 177 | // We start the comment with the main body based on the comments from the |
| 178 | // .proto file (if present). We then continue with the field declaration, |
| 179 | // e.g.: |
| 180 | // optional string foo = 5; |
| 181 | // And then we end with the javadoc tags if applicable. |
| 182 | // If the field is a group, the debug string might end with {. |
| 183 | printer->Print(text: "/**\n" ); |
| 184 | WriteDocCommentBody(printer, descriptor: field); |
| 185 | printer->Print(text: " * <code>$def$</code>\n" , args: "def" , |
| 186 | args: EscapeJavadoc(input: FirstLineOf(value: field->DebugString()))); |
| 187 | printer->Print(text: " */\n" ); |
| 188 | } |
| 189 | |
| 190 | void WriteDeprecatedJavadoc(io::Printer* printer, const FieldDescriptor* field, |
| 191 | const FieldAccessorType type) { |
| 192 | if (!field->options().deprecated()) { |
| 193 | return; |
| 194 | } |
| 195 | |
| 196 | // Lite codegen does not annotate set & clear methods with @Deprecated. |
| 197 | if (field->file()->options().optimize_for() == FileOptions::LITE_RUNTIME && |
| 198 | (type == SETTER || type == CLEARER)) { |
| 199 | return; |
| 200 | } |
| 201 | |
| 202 | std::string startLine = "0" ; |
| 203 | SourceLocation location; |
| 204 | if (field->GetSourceLocation(out_location: &location)) { |
| 205 | startLine = std::to_string(val: location.start_line); |
| 206 | } |
| 207 | |
| 208 | printer->Print(text: " * @deprecated $name$ is deprecated.\n" , args: "name" , |
| 209 | args: field->full_name()); |
| 210 | printer->Print(text: " * See $file$;l=$line$\n" , args: "file" , args: field->file()->name(), |
| 211 | args: "line" , args: startLine); |
| 212 | } |
| 213 | |
| 214 | void (io::Printer* printer, |
| 215 | const FieldDescriptor* field, |
| 216 | const FieldAccessorType type, |
| 217 | const bool builder) { |
| 218 | printer->Print(text: "/**\n" ); |
| 219 | WriteDocCommentBody(printer, descriptor: field); |
| 220 | printer->Print(text: " * <code>$def$</code>\n" , args: "def" , |
| 221 | args: EscapeJavadoc(input: FirstLineOf(value: field->DebugString()))); |
| 222 | WriteDeprecatedJavadoc(printer, field, type); |
| 223 | switch (type) { |
| 224 | case HAZZER: |
| 225 | printer->Print(text: " * @return Whether the $name$ field is set.\n" , args: "name" , |
| 226 | args: field->camelcase_name()); |
| 227 | break; |
| 228 | case GETTER: |
| 229 | printer->Print(text: " * @return The $name$.\n" , args: "name" , |
| 230 | args: field->camelcase_name()); |
| 231 | break; |
| 232 | case SETTER: |
| 233 | printer->Print(text: " * @param value The $name$ to set.\n" , args: "name" , |
| 234 | args: field->camelcase_name()); |
| 235 | break; |
| 236 | case CLEARER: |
| 237 | // Print nothing |
| 238 | break; |
| 239 | // Repeated |
| 240 | case LIST_COUNT: |
| 241 | printer->Print(text: " * @return The count of $name$.\n" , args: "name" , |
| 242 | args: field->camelcase_name()); |
| 243 | break; |
| 244 | case LIST_GETTER: |
| 245 | printer->Print(text: " * @return A list containing the $name$.\n" , args: "name" , |
| 246 | args: field->camelcase_name()); |
| 247 | break; |
| 248 | case LIST_INDEXED_GETTER: |
| 249 | printer->Print(text: " * @param index The index of the element to return.\n" ); |
| 250 | printer->Print(text: " * @return The $name$ at the given index.\n" , args: "name" , |
| 251 | args: field->camelcase_name()); |
| 252 | break; |
| 253 | case LIST_INDEXED_SETTER: |
| 254 | printer->Print(text: " * @param index The index to set the value at.\n" ); |
| 255 | printer->Print(text: " * @param value The $name$ to set.\n" , args: "name" , |
| 256 | args: field->camelcase_name()); |
| 257 | break; |
| 258 | case LIST_ADDER: |
| 259 | printer->Print(text: " * @param value The $name$ to add.\n" , args: "name" , |
| 260 | args: field->camelcase_name()); |
| 261 | break; |
| 262 | case LIST_MULTI_ADDER: |
| 263 | printer->Print(text: " * @param values The $name$ to add.\n" , args: "name" , |
| 264 | args: field->camelcase_name()); |
| 265 | break; |
| 266 | } |
| 267 | if (builder) { |
| 268 | printer->Print(text: " * @return This builder for chaining.\n" ); |
| 269 | } |
| 270 | printer->Print(text: " */\n" ); |
| 271 | } |
| 272 | |
| 273 | void (io::Printer* printer, |
| 274 | const FieldDescriptor* field, |
| 275 | const FieldAccessorType type, |
| 276 | const bool builder) { |
| 277 | printer->Print(text: "/**\n" ); |
| 278 | WriteDocCommentBody(printer, descriptor: field); |
| 279 | printer->Print(text: " * <code>$def$</code>\n" , args: "def" , |
| 280 | args: EscapeJavadoc(input: FirstLineOf(value: field->DebugString()))); |
| 281 | WriteDeprecatedJavadoc(printer, field, type); |
| 282 | switch (type) { |
| 283 | case HAZZER: |
| 284 | // Should never happen |
| 285 | break; |
| 286 | case GETTER: |
| 287 | printer->Print( |
| 288 | text: " * @return The enum numeric value on the wire for $name$.\n" , args: "name" , |
| 289 | args: field->camelcase_name()); |
| 290 | break; |
| 291 | case SETTER: |
| 292 | printer->Print( |
| 293 | text: " * @param value The enum numeric value on the wire for $name$ to " |
| 294 | "set.\n" , |
| 295 | args: "name" , args: field->camelcase_name()); |
| 296 | break; |
| 297 | case CLEARER: |
| 298 | // Print nothing |
| 299 | break; |
| 300 | // Repeated |
| 301 | case LIST_COUNT: |
| 302 | // Should never happen |
| 303 | break; |
| 304 | case LIST_GETTER: |
| 305 | printer->Print( |
| 306 | text: " * @return A list containing the enum numeric values on the wire " |
| 307 | "for $name$.\n" , |
| 308 | args: "name" , args: field->camelcase_name()); |
| 309 | break; |
| 310 | case LIST_INDEXED_GETTER: |
| 311 | printer->Print(text: " * @param index The index of the value to return.\n" ); |
| 312 | printer->Print( |
| 313 | text: " * @return The enum numeric value on the wire of $name$ at the " |
| 314 | "given index.\n" , |
| 315 | args: "name" , args: field->camelcase_name()); |
| 316 | break; |
| 317 | case LIST_INDEXED_SETTER: |
| 318 | printer->Print(text: " * @param index The index to set the value at.\n" ); |
| 319 | printer->Print( |
| 320 | text: " * @param value The enum numeric value on the wire for $name$ to " |
| 321 | "set.\n" , |
| 322 | args: "name" , args: field->camelcase_name()); |
| 323 | break; |
| 324 | case LIST_ADDER: |
| 325 | printer->Print( |
| 326 | text: " * @param value The enum numeric value on the wire for $name$ to " |
| 327 | "add.\n" , |
| 328 | args: "name" , args: field->camelcase_name()); |
| 329 | break; |
| 330 | case LIST_MULTI_ADDER: |
| 331 | printer->Print( |
| 332 | text: " * @param values The enum numeric values on the wire for $name$ to " |
| 333 | "add.\n" , |
| 334 | args: "name" , args: field->camelcase_name()); |
| 335 | break; |
| 336 | } |
| 337 | if (builder) { |
| 338 | printer->Print(text: " * @return This builder for chaining.\n" ); |
| 339 | } |
| 340 | printer->Print(text: " */\n" ); |
| 341 | } |
| 342 | |
| 343 | void (io::Printer* printer, |
| 344 | const FieldDescriptor* field, |
| 345 | const FieldAccessorType type, |
| 346 | const bool builder) { |
| 347 | printer->Print(text: "/**\n" ); |
| 348 | WriteDocCommentBody(printer, descriptor: field); |
| 349 | printer->Print(text: " * <code>$def$</code>\n" , args: "def" , |
| 350 | args: EscapeJavadoc(input: FirstLineOf(value: field->DebugString()))); |
| 351 | WriteDeprecatedJavadoc(printer, field, type); |
| 352 | switch (type) { |
| 353 | case HAZZER: |
| 354 | // Should never happen |
| 355 | break; |
| 356 | case GETTER: |
| 357 | printer->Print(text: " * @return The bytes for $name$.\n" , args: "name" , |
| 358 | args: field->camelcase_name()); |
| 359 | break; |
| 360 | case SETTER: |
| 361 | printer->Print(text: " * @param value The bytes for $name$ to set.\n" , args: "name" , |
| 362 | args: field->camelcase_name()); |
| 363 | break; |
| 364 | case CLEARER: |
| 365 | // Print nothing |
| 366 | break; |
| 367 | // Repeated |
| 368 | case LIST_COUNT: |
| 369 | // Should never happen |
| 370 | break; |
| 371 | case LIST_GETTER: |
| 372 | printer->Print(text: " * @return A list containing the bytes for $name$.\n" , |
| 373 | args: "name" , args: field->camelcase_name()); |
| 374 | break; |
| 375 | case LIST_INDEXED_GETTER: |
| 376 | printer->Print(text: " * @param index The index of the value to return.\n" ); |
| 377 | printer->Print(text: " * @return The bytes of the $name$ at the given index.\n" , |
| 378 | args: "name" , args: field->camelcase_name()); |
| 379 | break; |
| 380 | case LIST_INDEXED_SETTER: |
| 381 | printer->Print(text: " * @param index The index to set the value at.\n" ); |
| 382 | printer->Print(text: " * @param value The bytes of the $name$ to set.\n" , |
| 383 | args: "name" , args: field->camelcase_name()); |
| 384 | break; |
| 385 | case LIST_ADDER: |
| 386 | printer->Print(text: " * @param value The bytes of the $name$ to add.\n" , |
| 387 | args: "name" , args: field->camelcase_name()); |
| 388 | break; |
| 389 | case LIST_MULTI_ADDER: |
| 390 | printer->Print(text: " * @param values The bytes of the $name$ to add.\n" , |
| 391 | args: "name" , args: field->camelcase_name()); |
| 392 | break; |
| 393 | } |
| 394 | if (builder) { |
| 395 | printer->Print(text: " * @return This builder for chaining.\n" ); |
| 396 | } |
| 397 | printer->Print(text: " */\n" ); |
| 398 | } |
| 399 | |
| 400 | // Enum |
| 401 | |
| 402 | void (io::Printer* printer, const EnumDescriptor* enum_) { |
| 403 | printer->Print(text: "/**\n" ); |
| 404 | WriteDocCommentBody(printer, descriptor: enum_); |
| 405 | printer->Print( |
| 406 | text: " * Protobuf enum {@code $fullname$}\n" |
| 407 | " */\n" , |
| 408 | args: "fullname" , args: EscapeJavadoc(input: enum_->full_name())); |
| 409 | } |
| 410 | |
| 411 | void (io::Printer* printer, |
| 412 | const EnumValueDescriptor* value) { |
| 413 | printer->Print(text: "/**\n" ); |
| 414 | WriteDocCommentBody(printer, descriptor: value); |
| 415 | printer->Print( |
| 416 | text: " * <code>$def$</code>\n" |
| 417 | " */\n" , |
| 418 | args: "def" , args: EscapeJavadoc(input: FirstLineOf(value: value->DebugString()))); |
| 419 | } |
| 420 | |
| 421 | void (io::Printer* printer, |
| 422 | const ServiceDescriptor* service) { |
| 423 | printer->Print(text: "/**\n" ); |
| 424 | WriteDocCommentBody(printer, descriptor: service); |
| 425 | printer->Print( |
| 426 | text: " * Protobuf service {@code $fullname$}\n" |
| 427 | " */\n" , |
| 428 | args: "fullname" , args: EscapeJavadoc(input: service->full_name())); |
| 429 | } |
| 430 | |
| 431 | void (io::Printer* printer, |
| 432 | const MethodDescriptor* method) { |
| 433 | printer->Print(text: "/**\n" ); |
| 434 | WriteDocCommentBody(printer, descriptor: method); |
| 435 | printer->Print( |
| 436 | text: " * <code>$def$</code>\n" |
| 437 | " */\n" , |
| 438 | args: "def" , args: EscapeJavadoc(input: FirstLineOf(value: method->DebugString()))); |
| 439 | } |
| 440 | |
| 441 | } // namespace java |
| 442 | } // namespace compiler |
| 443 | } // namespace protobuf |
| 444 | } // namespace google |
| 445 | |