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
43namespace google {
44namespace protobuf {
45namespace compiler {
46namespace java {
47
48std::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: "&#42;");
61 } else {
62 result.push_back(c: c);
63 }
64 break;
65 case '/':
66 // Avoid "*/".
67 if (prev == '*') {
68 result.append(s: "&#47;");
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: "&#64;");
78 break;
79 case '<':
80 // Avoid interpretation as HTML.
81 result.append(s: "&lt;");
82 break;
83 case '>':
84 // Avoid interpretation as HTML.
85 result.append(s: "&gt;");
86 break;
87 case '&':
88 // Avoid interpretation as HTML.
89 result.append(s: "&amp;");
90 break;
91 case '\\':
92 // Java interprets Unicode escape sequences anywhere!
93 result.append(s: "&#92;");
94 break;
95 default:
96 result.push_back(c: c);
97 break;
98 }
99
100 prev = c;
101 }
102
103 return result;
104}
105
106static void WriteDocCommentBodyForLocation(io::Printer* printer,
107 const SourceLocation& location) {
108 std::string comments = 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
142template <typename DescriptorType>
143static void WriteDocCommentBody(io::Printer* printer,
144 const DescriptorType* descriptor) {
145 SourceLocation location;
146 if (descriptor->GetSourceLocation(&location)) {
147 WriteDocCommentBodyForLocation(printer, location);
148 }
149}
150
151static 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
167void WriteMessageDocComment(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
176void WriteFieldDocComment(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
190void 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
214void WriteFieldAccessorDocComment(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
273void WriteFieldEnumValueAccessorDocComment(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
343void WriteFieldStringBytesAccessorDocComment(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
402void WriteEnumDocComment(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
411void WriteEnumValueDocComment(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
421void WriteServiceDocComment(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
431void WriteMethodDocComment(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