blob: 616ccc4b6b0cb56b5c189199bbb958c5aacd88de [file] [log] [blame]
Ben Dodson920dbbb2010-08-04 15:21:06 -07001/*
2 * Copyright (C) 2010 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.doclava;
18
19import java.util.regex.Pattern;
20import java.util.regex.Matcher;
21import java.util.ArrayList;
C. Sean Youngda4b2e22015-05-18 14:12:30 -050022import java.util.Arrays;
23import java.util.HashSet;
24import java.util.Set;
Ben Dodson920dbbb2010-08-04 15:21:06 -070025
26public class Comment {
Ben Dodson920dbbb2010-08-04 15:21:06 -070027 static final Pattern FIRST_SENTENCE =
28 Pattern.compile("((.*?)\\.)[ \t\r\n\\<](.*)", Pattern.DOTALL);
29
C. Sean Youngda4b2e22015-05-18 14:12:30 -050030 private static final Set<String> KNOWN_TAGS = new HashSet<String>(Arrays.asList(new String[] {
Joe Onorato7a6456c2010-09-16 12:36:36 -040031 "@author",
32 "@since",
33 "@version",
34 "@deprecated",
35 "@undeprecate",
36 "@docRoot",
37 "@sdkCurrent",
38 "@inheritDoc",
39 "@more",
40 "@samplecode",
41 "@sample",
42 "@include",
43 "@serial",
C. Sean Youngda4b2e22015-05-18 14:12:30 -050044 }));
Ben Dodson920dbbb2010-08-04 15:21:06 -070045
46 public Comment(String text, ContainerInfo base, SourcePositionInfo sp) {
47 mText = text;
48 mBase = base;
49 // sp now points to the end of the text, not the beginning!
50 mPosition = SourcePositionInfo.findBeginning(sp, text);
51 }
52
Andrew Sappersteinf959ed12011-06-23 10:24:40 -070053 private void parseCommentTags(String text) {
54 int i = 0;
55 int length = text.length();
56 while (i < length && isWhitespaceChar(text.charAt(i++))) {}
Ben Dodson920dbbb2010-08-04 15:21:06 -070057
Andrew Sappersteinf959ed12011-06-23 10:24:40 -070058 if (i <= 0) {
59 return;
60 }
Ben Dodson920dbbb2010-08-04 15:21:06 -070061
Andrew Sappersteinf959ed12011-06-23 10:24:40 -070062 text = text.substring(i-1);
63 length = text.length();
Ben Dodson920dbbb2010-08-04 15:21:06 -070064
Andrew Sappersteinf959ed12011-06-23 10:24:40 -070065 if ("".equals(text)) {
66 return;
67 }
Ben Dodson920dbbb2010-08-04 15:21:06 -070068
Andrew Sappersteinf959ed12011-06-23 10:24:40 -070069 int start = 0;
70 int end = findStartOfBlock(text, start);
Ben Dodson920dbbb2010-08-04 15:21:06 -070071
Andrew Sappersteinf959ed12011-06-23 10:24:40 -070072
73 // possible scenarios
74 // main and block(s)
75 // main only (end == -1)
76 // block(s) only (end == 0)
77
78 switch (end) {
79 case -1: // main only
80 parseMainDescription(text, start, length);
81 return;
82 case 0: // block(s) only
83 break;
84 default: // main and block
85
86 // find end of main because end is really the beginning of @
87 parseMainDescription(text, start, findEndOfMainOrBlock(text, start, end));
88 break;
89 }
90
91 // parse blocks
92 for (start = end; start < length; start = end) {
93 end = findStartOfBlock(text, start+1);
94
95 if (end == -1) {
96 parseBlock(text, start, length);
97 break;
98 } else {
99 parseBlock(text, start, findEndOfMainOrBlock(text, start, end));
100 }
101 }
102
103 // for each block
104 // make block parts
105 // end is either next @ at beginning of line or end of text
Ben Dodson920dbbb2010-08-04 15:21:06 -0700106 }
107
Andrew Sappersteinf959ed12011-06-23 10:24:40 -0700108 private int findEndOfMainOrBlock(String text, int start, int end) {
109 for (int i = end-1; i >= start; i--) {
110 if (!isWhitespaceChar(text.charAt(i))) {
111 end = i+1;
112 break;
113 }
Ben Dodson920dbbb2010-08-04 15:21:06 -0700114 }
Andrew Sappersteinf959ed12011-06-23 10:24:40 -0700115 return end;
116 }
117
118 private void parseMainDescription(String mainDescription, int start, int end) {
119 if (mainDescription == null) {
120 return;
121 }
122
123 SourcePositionInfo pos = SourcePositionInfo.add(mPosition, mText, 0);
124 while (start < end) {
125 int startOfInlineTag = findStartIndexOfInlineTag(mainDescription, start, end);
126
127 // if there are no more tags
128 if (startOfInlineTag == -1) {
129 tag(null, mainDescription.substring(start, end), true, pos);
130 return;
131 }
132
133 //int endOfInlineTag = mainDescription.indexOf('}', startOfInlineTag);
134 int endOfInlineTag = findEndIndexOfInlineTag(mainDescription, startOfInlineTag, end);
135
136 // if there was only beginning tag
137 if (endOfInlineTag == -1) {
138 // parse all of main as one tag
139 tag(null, mainDescription.substring(start, end), true, pos);
140 return;
141 }
142
143 endOfInlineTag++; // add one to make it a proper ending index
144
145 // do first part without an inline tag - ie, just plaintext
146 tag(null, mainDescription.substring(start, startOfInlineTag), true, pos);
147
148 // parse the rest of this section, the inline tag
149 parseInlineTag(mainDescription, startOfInlineTag, endOfInlineTag, pos);
150
151 // keep going
152 start = endOfInlineTag;
153 }
154 }
155
156 private int findStartIndexOfInlineTag(String text, int fromIndex, int toIndex) {
157 for (int i = fromIndex; i < (toIndex-3); i++) {
158 if (text.charAt(i) == '{' && text.charAt(i+1) == '@' && !isWhitespaceChar(text.charAt(i+2))) {
159 return i;
160 }
161 }
162
163 return -1;
164 }
165
166 private int findEndIndexOfInlineTag(String text, int fromIndex, int toIndex) {
167 for (int i = fromIndex; i < toIndex; i++) {
168 if (text.charAt(i) == '}') {
169 return i;
170 }
171 }
172
173 return -1;
174 }
175
176 private void parseInlineTag(String text, int start, int end, SourcePositionInfo pos) {
177 int index = start+1;
178 //int len = text.length();
179 char c = text.charAt(index);
180 // find the end of the tag name "@something"
181 // need to do something special if we have '}'
182 while (index < end && !isWhitespaceChar(c)) {
183
184 // if this tag has no value, just return with tag name only
185 if (c == '}') {
186 // TODO - should value be "" or null?
187 tag(text.substring(start+1, end), null, true, pos);
188 return;
189 }
190 c = text.charAt(index++);
191 }
192
193 // don't parse things that don't have at least one extra character after @
194 // probably should be plus 3
195 // TODO - remove this - think it's fixed by change in parseMainDescription
196 if (index == start+3) {
197 return;
198 }
199
200 int endOfFirstPart = index-1;
201
202 // get to beginning of tag value
203 while (index < end && isWhitespaceChar(text.charAt(index++))) {}
204 int startOfSecondPart = index-1;
205
206 // +1 to get rid of opening brace and -1 to get rid of closing brace
207 // maybe i wanna make this more elegant
Scott Main99e4ebe2012-11-30 16:06:59 -0800208 String tagName = text.substring(start+1, endOfFirstPart);
209 String tagText = text.substring(startOfSecondPart, end-1);
Scott Main99e4ebe2012-11-30 16:06:59 -0800210 tag(tagName, tagText, true, pos);
Andrew Sappersteinf959ed12011-06-23 10:24:40 -0700211 }
212
213
214 /**
215 * Finds the index of the start of a new block comment or -1 if there are
216 * no more starts.
217 * @param text The String to search
218 * @param start the index of the String to start searching
219 * @return The index of the start of a new block comment or -1 if there are
220 * no more starts.
221 */
222 private int findStartOfBlock(String text, int start) {
223 // how to detect we're at a new @
224 // if the chars to the left of it are \r or \n, we're at one
225 // if the chars to the left of it are ' ' or \t, keep looking
226 // otherwise, we're in the middle of a block, keep looking
227 int index = text.indexOf('@', start);
228
229 // no @ in text or index at first position
230 if (index == -1 ||
231 (index == 0 && text.length() > 1 && !isWhitespaceChar(text.charAt(index+1)))) {
232 return index;
233 }
234
235 index = getPossibleStartOfBlock(text, index);
236
237 int i = index-1; // start at the character immediately to the left of @
238 char c;
239 while (i >= 0) {
240 c = text.charAt(i--);
241
242 // found a new block comment because we're at the beginning of a line
243 if (c == '\r' || c == '\n') {
244 return index;
245 }
246
247 // there is a non whitespace character to the left of the @
248 // before finding a new line, keep searching
249 if (c != ' ' && c != '\t') {
250 index = getPossibleStartOfBlock(text, index+1);
251 i = index-1;
252 }
253
254 // some whitespace character, so keep looking, we might be at a new block comment
255 }
256
257 return -1;
258 }
259
260 private int getPossibleStartOfBlock(String text, int index) {
261 while (isWhitespaceChar(text.charAt(index+1)) || !isWhitespaceChar(text.charAt(index-1))) {
262 index = text.indexOf('@', index+1);
263
264 if (index == -1 || index == text.length()-1) {
265 return -1;
266 }
267 }
268
269 return index;
270 }
271
272 private void parseBlock(String text, int startOfBlock, int endOfBlock) {
273 SourcePositionInfo pos = SourcePositionInfo.add(mPosition, mText, startOfBlock);
274 int index = startOfBlock;
275
276 for (char c = text.charAt(index);
277 index < endOfBlock && !isWhitespaceChar(c); c = text.charAt(index++)) {}
278
279 //
280 if (index == startOfBlock+1) {
281 return;
282 }
283
284 int endOfFirstPart = index-1;
285 if (index == endOfBlock) {
286 // TODO - should value be null or ""
287 tag(text.substring(startOfBlock,
288 findEndOfMainOrBlock(text, startOfBlock, index)), "", false, pos);
289 return;
290 }
291
292
293 // get to beginning of tag value
294 while (index < endOfBlock && isWhitespaceChar(text.charAt(index++))) {}
295 int startOfSecondPart = index-1;
296
297 tag(text.substring(startOfBlock, endOfFirstPart),
298 text.substring(startOfSecondPart, endOfBlock), false, pos);
299 }
300
301 private boolean isWhitespaceChar(char c) {
C. Sean Youngda4b2e22015-05-18 14:12:30 -0500302 switch (c) {
303 case ' ':
304 case '\r':
305 case '\t':
306 case '\n':
307 return true;
308 }
309 return false;
Ben Dodson920dbbb2010-08-04 15:21:06 -0700310 }
311
312 private void tag(String name, String text, boolean isInline, SourcePositionInfo pos) {
313 /*
314 * String s = isInline ? "inline" : "outofline"; System.out.println("---> " + s + " name=[" +
315 * name + "] text=[" + text + "]");
316 */
317 if (name == null) {
318 mInlineTagsList.add(new TextTagInfo("Text", "Text", text, pos));
319 } else if (name.equals("@param")) {
320 mParamTagsList.add(new ParamTagInfo("@param", "@param", text, mBase, pos));
321 } else if (name.equals("@see")) {
322 mSeeTagsList.add(new SeeTagInfo("@see", "@see", text, mBase, pos));
Jeff Arneson92393f12015-01-05 13:40:46 -0800323 } else if (name.equals("@link")) {
Ben Dodson920dbbb2010-08-04 15:21:06 -0700324 mInlineTagsList.add(new SeeTagInfo(name, "@see", text, mBase, pos));
Jeff Arneson92393f12015-01-05 13:40:46 -0800325 } else if (name.equals("@linkplain")) {
326 mInlineTagsList.add(new SeeTagInfo(name, "@linkplain", text, mBase, pos));
Scott Maine94aecf2012-11-30 15:19:51 -0800327 } else if (name.equals("@value")) {
328 mInlineTagsList.add(new SeeTagInfo(name, "@value", text, mBase, pos));
Ben Dodson920dbbb2010-08-04 15:21:06 -0700329 } else if (name.equals("@throws") || name.equals("@exception")) {
330 mThrowsTagsList.add(new ThrowsTagInfo("@throws", "@throws", text, mBase, pos));
331 } else if (name.equals("@return")) {
332 mReturnTagsList.add(new ParsedTagInfo("@return", "@return", text, mBase, pos));
333 } else if (name.equals("@deprecated")) {
334 if (text.length() == 0) {
335 Errors.error(Errors.MISSING_COMMENT, pos, "@deprecated tag with no explanatory comment");
336 text = "No replacement.";
337 }
338 mDeprecatedTagsList.add(new ParsedTagInfo("@deprecated", "@deprecated", text, mBase, pos));
339 } else if (name.equals("@literal")) {
340 mInlineTagsList.add(new LiteralTagInfo(text, pos));
341 } else if (name.equals("@code")) {
342 mInlineTagsList.add(new CodeTagInfo(text, pos));
Svetoslav59aecf82015-06-05 16:24:18 -0700343 } else if (name.equals("@hide") || name.equals("@removed")
344 || name.equals("@pending") || name.equals("@doconly")) {
Ben Dodson920dbbb2010-08-04 15:21:06 -0700345 // nothing
346 } else if (name.equals("@attr")) {
347 AttrTagInfo tag = new AttrTagInfo("@attr", "@attr", text, mBase, pos);
348 mAttrTagsList.add(tag);
349 Comment c = tag.description();
350 if (c != null) {
351 for (TagInfo t : c.tags()) {
352 mInlineTagsList.add(t);
353 }
354 }
355 } else if (name.equals("@undeprecate")) {
356 mUndeprecateTagsList.add(new TextTagInfo("@undeprecate", "@undeprecate", text, pos));
357 } else if (name.equals("@include") || name.equals("@sample")) {
358 mInlineTagsList.add(new SampleTagInfo(name, "@include", text, mBase, pos));
359 } else {
C. Sean Youngda4b2e22015-05-18 14:12:30 -0500360 boolean known = KNOWN_TAGS.contains(name);
Ben Dodson920dbbb2010-08-04 15:21:06 -0700361 if (!known) {
Joe Onorato7a6456c2010-09-16 12:36:36 -0400362 known = Doclava.knownTags.contains(name);
363 }
364 if (!known) {
Ben Dodson920dbbb2010-08-04 15:21:06 -0700365 Errors.error(Errors.UNKNOWN_TAG, pos == null ? null : new SourcePositionInfo(pos),
366 "Unknown tag: " + name);
367 }
368 TagInfo t = new TextTagInfo(name, name, text, pos);
369 if (isInline) {
370 mInlineTagsList.add(t);
371 } else {
372 mTagsList.add(t);
373 }
374 }
375 }
376
377 private void parseBriefTags() {
378 int N = mInlineTagsList.size();
379
380 // look for "@more" tag, which means that we might go past the first sentence.
381 int more = -1;
382 for (int i = 0; i < N; i++) {
383 if (mInlineTagsList.get(i).name().equals("@more")) {
384 more = i;
385 }
386 }
387 if (more >= 0) {
388 for (int i = 0; i < more; i++) {
389 mBriefTagsList.add(mInlineTagsList.get(i));
390 }
391 } else {
392 for (int i = 0; i < N; i++) {
393 TagInfo t = mInlineTagsList.get(i);
394 if (t.name().equals("Text")) {
395 Matcher m = FIRST_SENTENCE.matcher(t.text());
396 if (m.matches()) {
397 String text = m.group(1);
398 TagInfo firstSentenceTag = new TagInfo(t.name(), t.kind(), text, t.position());
399 mBriefTagsList.add(firstSentenceTag);
400 break;
401 }
402 }
403 mBriefTagsList.add(t);
404
405 }
406 }
407 }
408
409 public TagInfo[] tags() {
410 init();
411 return mInlineTags;
412 }
413
414 public TagInfo[] tags(String name) {
415 init();
416 ArrayList<TagInfo> results = new ArrayList<TagInfo>();
417 int N = mInlineTagsList.size();
418 for (int i = 0; i < N; i++) {
419 TagInfo t = mInlineTagsList.get(i);
420 if (t.name().equals(name)) {
421 results.add(t);
422 }
423 }
C. Sean Youngda4b2e22015-05-18 14:12:30 -0500424 return results.toArray(TagInfo.getArray(results.size()));
Ben Dodson920dbbb2010-08-04 15:21:06 -0700425 }
426
427 public ParamTagInfo[] paramTags() {
428 init();
429 return mParamTags;
430 }
431
432 public SeeTagInfo[] seeTags() {
433 init();
434 return mSeeTags;
435 }
436
437 public ThrowsTagInfo[] throwsTags() {
438 init();
439 return mThrowsTags;
440 }
441
442 public TagInfo[] returnTags() {
443 init();
444 return mReturnTags;
445 }
446
447 public TagInfo[] deprecatedTags() {
448 init();
449 return mDeprecatedTags;
450 }
451
452 public TagInfo[] undeprecateTags() {
453 init();
454 return mUndeprecateTags;
455 }
456
457 public AttrTagInfo[] attrTags() {
458 init();
459 return mAttrTags;
460 }
461
462 public TagInfo[] briefTags() {
463 init();
464 return mBriefTags;
465 }
466
467 public boolean isHidden() {
Hui Shu5118ffe2014-02-18 14:06:42 -0800468 if (mHidden == null) {
469 mHidden = !Doclava.checkLevel(Doclava.SHOW_HIDDEN) &&
470 (mText != null) && (mText.indexOf("@hide") >= 0 || mText.indexOf("@pending") >= 0);
Ben Dodson920dbbb2010-08-04 15:21:06 -0700471 }
Hui Shu5118ffe2014-02-18 14:06:42 -0800472 return mHidden;
473 }
474
475 public boolean isRemoved() {
476 if (mRemoved == null) {
477 mRemoved = !Doclava.checkLevel(Doclava.SHOW_HIDDEN) &&
478 (mText != null) && (mText.indexOf("@removed") >= 0);
479 }
480
481 return mRemoved;
Ben Dodson920dbbb2010-08-04 15:21:06 -0700482 }
483
484 public boolean isDocOnly() {
Hui Shu5118ffe2014-02-18 14:06:42 -0800485 if (mDocOnly == null) {
486 mDocOnly = (mText != null) && (mText.indexOf("@doconly") >= 0);
Ben Dodson920dbbb2010-08-04 15:21:06 -0700487 }
Hui Shu5118ffe2014-02-18 14:06:42 -0800488 return mDocOnly;
Ben Dodson920dbbb2010-08-04 15:21:06 -0700489 }
Hui Shu5118ffe2014-02-18 14:06:42 -0800490
Ben Dodson920dbbb2010-08-04 15:21:06 -0700491 public boolean isDeprecated() {
Hui Shu5118ffe2014-02-18 14:06:42 -0800492 if (mDeprecated == null) {
493 mDeprecated = (mText != null) && (mText.indexOf("@deprecated") >= 0);
Ben Dodson920dbbb2010-08-04 15:21:06 -0700494 }
Hui Shu5118ffe2014-02-18 14:06:42 -0800495
496 return mDeprecated;
Ben Dodson920dbbb2010-08-04 15:21:06 -0700497 }
498
499 private void init() {
500 if (!mInitialized) {
501 initImpl();
502 }
503 }
504
505 private void initImpl() {
506 isHidden();
Hui Shu5118ffe2014-02-18 14:06:42 -0800507 isRemoved();
Ben Dodson920dbbb2010-08-04 15:21:06 -0700508 isDocOnly();
509 isDeprecated();
510
511 // Don't bother parsing text if we aren't generating documentation.
Ben Dodson9ccd9e32010-08-06 17:18:52 -0700512 if (Doclava.parseComments()) {
Andrew Sappersteinf959ed12011-06-23 10:24:40 -0700513 parseCommentTags(mText);
514 parseBriefTags();
Ben Dodson920dbbb2010-08-04 15:21:06 -0700515 } else {
516 // Forces methods to be recognized by findOverriddenMethods in MethodInfo.
517 mInlineTagsList.add(new TextTagInfo("Text", "Text", mText,
518 SourcePositionInfo.add(mPosition, mText, 0)));
519 }
520
521 mText = null;
522 mInitialized = true;
523
C. Sean Youngda4b2e22015-05-18 14:12:30 -0500524 mInlineTags = mInlineTagsList.toArray(TagInfo.getArray(mInlineTagsList.size()));
525 mParamTags = mParamTagsList.toArray(ParamTagInfo.getArray(mParamTagsList.size()));
526 mSeeTags = mSeeTagsList.toArray(SeeTagInfo.getArray(mSeeTagsList.size()));
527 mThrowsTags = mThrowsTagsList.toArray(ThrowsTagInfo.getArray(mThrowsTagsList.size()));
528 mReturnTags = ParsedTagInfo.joinTags(
529 mReturnTagsList.toArray(ParsedTagInfo.getArray(mReturnTagsList.size())));
530 mDeprecatedTags = ParsedTagInfo.joinTags(
531 mDeprecatedTagsList.toArray(ParsedTagInfo.getArray(mDeprecatedTagsList.size())));
532 mUndeprecateTags = mUndeprecateTagsList.toArray(TagInfo.getArray(mUndeprecateTagsList.size()));
533 mAttrTags = mAttrTagsList.toArray(AttrTagInfo.getArray(mAttrTagsList.size()));
534 mBriefTags = mBriefTagsList.toArray(TagInfo.getArray(mBriefTagsList.size()));
Ben Dodson920dbbb2010-08-04 15:21:06 -0700535
536 mParamTagsList = null;
537 mSeeTagsList = null;
538 mThrowsTagsList = null;
539 mReturnTagsList = null;
540 mDeprecatedTagsList = null;
541 mUndeprecateTagsList = null;
542 mAttrTagsList = null;
543 mBriefTagsList = null;
544 }
545
546 boolean mInitialized;
Hui Shu5118ffe2014-02-18 14:06:42 -0800547 Boolean mHidden = null;
548 Boolean mRemoved = null;
549 Boolean mDocOnly = null;
550 Boolean mDeprecated = null;
Ben Dodson920dbbb2010-08-04 15:21:06 -0700551 String mText;
552 ContainerInfo mBase;
553 SourcePositionInfo mPosition;
554 int mLine = 1;
555
556 TagInfo[] mInlineTags;
557 TagInfo[] mTags;
558 ParamTagInfo[] mParamTags;
559 SeeTagInfo[] mSeeTags;
560 ThrowsTagInfo[] mThrowsTags;
561 TagInfo[] mBriefTags;
562 TagInfo[] mReturnTags;
563 TagInfo[] mDeprecatedTags;
564 TagInfo[] mUndeprecateTags;
565 AttrTagInfo[] mAttrTags;
566
567 ArrayList<TagInfo> mInlineTagsList = new ArrayList<TagInfo>();
568 ArrayList<TagInfo> mTagsList = new ArrayList<TagInfo>();
569 ArrayList<ParamTagInfo> mParamTagsList = new ArrayList<ParamTagInfo>();
570 ArrayList<SeeTagInfo> mSeeTagsList = new ArrayList<SeeTagInfo>();
571 ArrayList<ThrowsTagInfo> mThrowsTagsList = new ArrayList<ThrowsTagInfo>();
572 ArrayList<TagInfo> mBriefTagsList = new ArrayList<TagInfo>();
573 ArrayList<ParsedTagInfo> mReturnTagsList = new ArrayList<ParsedTagInfo>();
574 ArrayList<ParsedTagInfo> mDeprecatedTagsList = new ArrayList<ParsedTagInfo>();
575 ArrayList<TagInfo> mUndeprecateTagsList = new ArrayList<TagInfo>();
576 ArrayList<AttrTagInfo> mAttrTagsList = new ArrayList<AttrTagInfo>();
577
578
579}