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 | |