blob: cfb7aaf3907fe0123a3a75a6fcf26f1fd568e523 [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;
22
23public class Comment {
Ben Dodson920dbbb2010-08-04 15:21:06 -070024 static final Pattern FIRST_SENTENCE =
25 Pattern.compile("((.*?)\\.)[ \t\r\n\\<](.*)", Pattern.DOTALL);
26
Joe Onorato7a6456c2010-09-16 12:36:36 -040027 private static final String[] KNOWN_TAGS = new String[] {
28 "@author",
29 "@since",
30 "@version",
31 "@deprecated",
32 "@undeprecate",
33 "@docRoot",
34 "@sdkCurrent",
35 "@inheritDoc",
36 "@more",
37 "@samplecode",
38 "@sample",
39 "@include",
40 "@serial",
41 };
Ben Dodson920dbbb2010-08-04 15:21:06 -070042
43 public Comment(String text, ContainerInfo base, SourcePositionInfo sp) {
44 mText = text;
45 mBase = base;
46 // sp now points to the end of the text, not the beginning!
47 mPosition = SourcePositionInfo.findBeginning(sp, text);
48 }
49
Andrew Sappersteinf959ed12011-06-23 10:24:40 -070050 private void parseCommentTags(String text) {
51 int i = 0;
52 int length = text.length();
53 while (i < length && isWhitespaceChar(text.charAt(i++))) {}
Ben Dodson920dbbb2010-08-04 15:21:06 -070054
Andrew Sappersteinf959ed12011-06-23 10:24:40 -070055 if (i <= 0) {
56 return;
57 }
Ben Dodson920dbbb2010-08-04 15:21:06 -070058
Andrew Sappersteinf959ed12011-06-23 10:24:40 -070059 text = text.substring(i-1);
60 length = text.length();
Ben Dodson920dbbb2010-08-04 15:21:06 -070061
Andrew Sappersteinf959ed12011-06-23 10:24:40 -070062 if ("".equals(text)) {
63 return;
64 }
Ben Dodson920dbbb2010-08-04 15:21:06 -070065
Andrew Sappersteinf959ed12011-06-23 10:24:40 -070066 int start = 0;
67 int end = findStartOfBlock(text, start);
Ben Dodson920dbbb2010-08-04 15:21:06 -070068
Andrew Sappersteinf959ed12011-06-23 10:24:40 -070069
70 // possible scenarios
71 // main and block(s)
72 // main only (end == -1)
73 // block(s) only (end == 0)
74
75 switch (end) {
76 case -1: // main only
77 parseMainDescription(text, start, length);
78 return;
79 case 0: // block(s) only
80 break;
81 default: // main and block
82
83 // find end of main because end is really the beginning of @
84 parseMainDescription(text, start, findEndOfMainOrBlock(text, start, end));
85 break;
86 }
87
88 // parse blocks
89 for (start = end; start < length; start = end) {
90 end = findStartOfBlock(text, start+1);
91
92 if (end == -1) {
93 parseBlock(text, start, length);
94 break;
95 } else {
96 parseBlock(text, start, findEndOfMainOrBlock(text, start, end));
97 }
98 }
99
100 // for each block
101 // make block parts
102 // end is either next @ at beginning of line or end of text
Ben Dodson920dbbb2010-08-04 15:21:06 -0700103 }
104
Andrew Sappersteinf959ed12011-06-23 10:24:40 -0700105 private int findEndOfMainOrBlock(String text, int start, int end) {
106 for (int i = end-1; i >= start; i--) {
107 if (!isWhitespaceChar(text.charAt(i))) {
108 end = i+1;
109 break;
110 }
Ben Dodson920dbbb2010-08-04 15:21:06 -0700111 }
Andrew Sappersteinf959ed12011-06-23 10:24:40 -0700112 return end;
113 }
114
115 private void parseMainDescription(String mainDescription, int start, int end) {
116 if (mainDescription == null) {
117 return;
118 }
119
120 SourcePositionInfo pos = SourcePositionInfo.add(mPosition, mText, 0);
121 while (start < end) {
122 int startOfInlineTag = findStartIndexOfInlineTag(mainDescription, start, end);
123
124 // if there are no more tags
125 if (startOfInlineTag == -1) {
126 tag(null, mainDescription.substring(start, end), true, pos);
127 return;
128 }
129
130 //int endOfInlineTag = mainDescription.indexOf('}', startOfInlineTag);
131 int endOfInlineTag = findEndIndexOfInlineTag(mainDescription, startOfInlineTag, end);
132
133 // if there was only beginning tag
134 if (endOfInlineTag == -1) {
135 // parse all of main as one tag
136 tag(null, mainDescription.substring(start, end), true, pos);
137 return;
138 }
139
140 endOfInlineTag++; // add one to make it a proper ending index
141
142 // do first part without an inline tag - ie, just plaintext
143 tag(null, mainDescription.substring(start, startOfInlineTag), true, pos);
144
145 // parse the rest of this section, the inline tag
146 parseInlineTag(mainDescription, startOfInlineTag, endOfInlineTag, pos);
147
148 // keep going
149 start = endOfInlineTag;
150 }
151 }
152
153 private int findStartIndexOfInlineTag(String text, int fromIndex, int toIndex) {
154 for (int i = fromIndex; i < (toIndex-3); i++) {
155 if (text.charAt(i) == '{' && text.charAt(i+1) == '@' && !isWhitespaceChar(text.charAt(i+2))) {
156 return i;
157 }
158 }
159
160 return -1;
161 }
162
163 private int findEndIndexOfInlineTag(String text, int fromIndex, int toIndex) {
164 for (int i = fromIndex; i < toIndex; i++) {
165 if (text.charAt(i) == '}') {
166 return i;
167 }
168 }
169
170 return -1;
171 }
172
173 private void parseInlineTag(String text, int start, int end, SourcePositionInfo pos) {
174 int index = start+1;
175 //int len = text.length();
176 char c = text.charAt(index);
177 // find the end of the tag name "@something"
178 // need to do something special if we have '}'
179 while (index < end && !isWhitespaceChar(c)) {
180
181 // if this tag has no value, just return with tag name only
182 if (c == '}') {
183 // TODO - should value be "" or null?
184 tag(text.substring(start+1, end), null, true, pos);
185 return;
186 }
187 c = text.charAt(index++);
188 }
189
190 // don't parse things that don't have at least one extra character after @
191 // probably should be plus 3
192 // TODO - remove this - think it's fixed by change in parseMainDescription
193 if (index == start+3) {
194 return;
195 }
196
197 int endOfFirstPart = index-1;
198
199 // get to beginning of tag value
200 while (index < end && isWhitespaceChar(text.charAt(index++))) {}
201 int startOfSecondPart = index-1;
202
203 // +1 to get rid of opening brace and -1 to get rid of closing brace
204 // maybe i wanna make this more elegant
Scott Main99e4ebe2012-11-30 16:06:59 -0800205 String tagName = text.substring(start+1, endOfFirstPart);
206 String tagText = text.substring(startOfSecondPart, end-1);
Scott Main99e4ebe2012-11-30 16:06:59 -0800207 tag(tagName, tagText, true, pos);
Andrew Sappersteinf959ed12011-06-23 10:24:40 -0700208 }
209
210
211 /**
212 * Finds the index of the start of a new block comment or -1 if there are
213 * no more starts.
214 * @param text The String to search
215 * @param start the index of the String to start searching
216 * @return The index of the start of a new block comment or -1 if there are
217 * no more starts.
218 */
219 private int findStartOfBlock(String text, int start) {
220 // how to detect we're at a new @
221 // if the chars to the left of it are \r or \n, we're at one
222 // if the chars to the left of it are ' ' or \t, keep looking
223 // otherwise, we're in the middle of a block, keep looking
224 int index = text.indexOf('@', start);
225
226 // no @ in text or index at first position
227 if (index == -1 ||
228 (index == 0 && text.length() > 1 && !isWhitespaceChar(text.charAt(index+1)))) {
229 return index;
230 }
231
232 index = getPossibleStartOfBlock(text, index);
233
234 int i = index-1; // start at the character immediately to the left of @
235 char c;
236 while (i >= 0) {
237 c = text.charAt(i--);
238
239 // found a new block comment because we're at the beginning of a line
240 if (c == '\r' || c == '\n') {
241 return index;
242 }
243
244 // there is a non whitespace character to the left of the @
245 // before finding a new line, keep searching
246 if (c != ' ' && c != '\t') {
247 index = getPossibleStartOfBlock(text, index+1);
248 i = index-1;
249 }
250
251 // some whitespace character, so keep looking, we might be at a new block comment
252 }
253
254 return -1;
255 }
256
257 private int getPossibleStartOfBlock(String text, int index) {
258 while (isWhitespaceChar(text.charAt(index+1)) || !isWhitespaceChar(text.charAt(index-1))) {
259 index = text.indexOf('@', index+1);
260
261 if (index == -1 || index == text.length()-1) {
262 return -1;
263 }
264 }
265
266 return index;
267 }
268
269 private void parseBlock(String text, int startOfBlock, int endOfBlock) {
270 SourcePositionInfo pos = SourcePositionInfo.add(mPosition, mText, startOfBlock);
271 int index = startOfBlock;
272
273 for (char c = text.charAt(index);
274 index < endOfBlock && !isWhitespaceChar(c); c = text.charAt(index++)) {}
275
276 //
277 if (index == startOfBlock+1) {
278 return;
279 }
280
281 int endOfFirstPart = index-1;
282 if (index == endOfBlock) {
283 // TODO - should value be null or ""
284 tag(text.substring(startOfBlock,
285 findEndOfMainOrBlock(text, startOfBlock, index)), "", false, pos);
286 return;
287 }
288
289
290 // get to beginning of tag value
291 while (index < endOfBlock && isWhitespaceChar(text.charAt(index++))) {}
292 int startOfSecondPart = index-1;
293
294 tag(text.substring(startOfBlock, endOfFirstPart),
295 text.substring(startOfSecondPart, endOfBlock), false, pos);
296 }
297
298 private boolean isWhitespaceChar(char c) {
299 return c == ' ' || c == '\r' || c == '\t' || c == '\n';
Ben Dodson920dbbb2010-08-04 15:21:06 -0700300 }
301
302 private void tag(String name, String text, boolean isInline, SourcePositionInfo pos) {
303 /*
304 * String s = isInline ? "inline" : "outofline"; System.out.println("---> " + s + " name=[" +
305 * name + "] text=[" + text + "]");
306 */
307 if (name == null) {
308 mInlineTagsList.add(new TextTagInfo("Text", "Text", text, pos));
309 } else if (name.equals("@param")) {
310 mParamTagsList.add(new ParamTagInfo("@param", "@param", text, mBase, pos));
311 } else if (name.equals("@see")) {
312 mSeeTagsList.add(new SeeTagInfo("@see", "@see", text, mBase, pos));
Jeff Arneson92393f12015-01-05 13:40:46 -0800313 } else if (name.equals("@link")) {
Ben Dodson920dbbb2010-08-04 15:21:06 -0700314 mInlineTagsList.add(new SeeTagInfo(name, "@see", text, mBase, pos));
Jeff Arneson92393f12015-01-05 13:40:46 -0800315 } else if (name.equals("@linkplain")) {
316 mInlineTagsList.add(new SeeTagInfo(name, "@linkplain", text, mBase, pos));
Scott Maine94aecf2012-11-30 15:19:51 -0800317 } else if (name.equals("@value")) {
318 mInlineTagsList.add(new SeeTagInfo(name, "@value", text, mBase, pos));
Ben Dodson920dbbb2010-08-04 15:21:06 -0700319 } else if (name.equals("@throws") || name.equals("@exception")) {
320 mThrowsTagsList.add(new ThrowsTagInfo("@throws", "@throws", text, mBase, pos));
321 } else if (name.equals("@return")) {
322 mReturnTagsList.add(new ParsedTagInfo("@return", "@return", text, mBase, pos));
323 } else if (name.equals("@deprecated")) {
324 if (text.length() == 0) {
325 Errors.error(Errors.MISSING_COMMENT, pos, "@deprecated tag with no explanatory comment");
326 text = "No replacement.";
327 }
328 mDeprecatedTagsList.add(new ParsedTagInfo("@deprecated", "@deprecated", text, mBase, pos));
329 } else if (name.equals("@literal")) {
330 mInlineTagsList.add(new LiteralTagInfo(text, pos));
331 } else if (name.equals("@code")) {
332 mInlineTagsList.add(new CodeTagInfo(text, pos));
333 } else if (name.equals("@hide") || name.equals("@pending") || name.equals("@doconly")) {
334 // nothing
335 } else if (name.equals("@attr")) {
336 AttrTagInfo tag = new AttrTagInfo("@attr", "@attr", text, mBase, pos);
337 mAttrTagsList.add(tag);
338 Comment c = tag.description();
339 if (c != null) {
340 for (TagInfo t : c.tags()) {
341 mInlineTagsList.add(t);
342 }
343 }
344 } else if (name.equals("@undeprecate")) {
345 mUndeprecateTagsList.add(new TextTagInfo("@undeprecate", "@undeprecate", text, pos));
346 } else if (name.equals("@include") || name.equals("@sample")) {
347 mInlineTagsList.add(new SampleTagInfo(name, "@include", text, mBase, pos));
348 } else {
349 boolean known = false;
350 for (String s : KNOWN_TAGS) {
351 if (s.equals(name)) {
352 known = true;
353 break;
354 }
355 }
356 if (!known) {
Joe Onorato7a6456c2010-09-16 12:36:36 -0400357 known = Doclava.knownTags.contains(name);
358 }
359 if (!known) {
Ben Dodson920dbbb2010-08-04 15:21:06 -0700360 Errors.error(Errors.UNKNOWN_TAG, pos == null ? null : new SourcePositionInfo(pos),
361 "Unknown tag: " + name);
362 }
363 TagInfo t = new TextTagInfo(name, name, text, pos);
364 if (isInline) {
365 mInlineTagsList.add(t);
366 } else {
367 mTagsList.add(t);
368 }
369 }
370 }
371
372 private void parseBriefTags() {
373 int N = mInlineTagsList.size();
374
375 // look for "@more" tag, which means that we might go past the first sentence.
376 int more = -1;
377 for (int i = 0; i < N; i++) {
378 if (mInlineTagsList.get(i).name().equals("@more")) {
379 more = i;
380 }
381 }
382 if (more >= 0) {
383 for (int i = 0; i < more; i++) {
384 mBriefTagsList.add(mInlineTagsList.get(i));
385 }
386 } else {
387 for (int i = 0; i < N; i++) {
388 TagInfo t = mInlineTagsList.get(i);
389 if (t.name().equals("Text")) {
390 Matcher m = FIRST_SENTENCE.matcher(t.text());
391 if (m.matches()) {
392 String text = m.group(1);
393 TagInfo firstSentenceTag = new TagInfo(t.name(), t.kind(), text, t.position());
394 mBriefTagsList.add(firstSentenceTag);
395 break;
396 }
397 }
398 mBriefTagsList.add(t);
399
400 }
401 }
402 }
403
404 public TagInfo[] tags() {
405 init();
406 return mInlineTags;
407 }
408
409 public TagInfo[] tags(String name) {
410 init();
411 ArrayList<TagInfo> results = new ArrayList<TagInfo>();
412 int N = mInlineTagsList.size();
413 for (int i = 0; i < N; i++) {
414 TagInfo t = mInlineTagsList.get(i);
415 if (t.name().equals(name)) {
416 results.add(t);
417 }
418 }
419 return results.toArray(new TagInfo[results.size()]);
420 }
421
422 public ParamTagInfo[] paramTags() {
423 init();
424 return mParamTags;
425 }
426
427 public SeeTagInfo[] seeTags() {
428 init();
429 return mSeeTags;
430 }
431
432 public ThrowsTagInfo[] throwsTags() {
433 init();
434 return mThrowsTags;
435 }
436
437 public TagInfo[] returnTags() {
438 init();
439 return mReturnTags;
440 }
441
442 public TagInfo[] deprecatedTags() {
443 init();
444 return mDeprecatedTags;
445 }
446
447 public TagInfo[] undeprecateTags() {
448 init();
449 return mUndeprecateTags;
450 }
451
452 public AttrTagInfo[] attrTags() {
453 init();
454 return mAttrTags;
455 }
456
457 public TagInfo[] briefTags() {
458 init();
459 return mBriefTags;
460 }
461
462 public boolean isHidden() {
Hui Shu5118ffe2014-02-18 14:06:42 -0800463 if (mHidden == null) {
464 mHidden = !Doclava.checkLevel(Doclava.SHOW_HIDDEN) &&
465 (mText != null) && (mText.indexOf("@hide") >= 0 || mText.indexOf("@pending") >= 0);
Ben Dodson920dbbb2010-08-04 15:21:06 -0700466 }
Hui Shu5118ffe2014-02-18 14:06:42 -0800467 return mHidden;
468 }
469
470 public boolean isRemoved() {
471 if (mRemoved == null) {
472 mRemoved = !Doclava.checkLevel(Doclava.SHOW_HIDDEN) &&
473 (mText != null) && (mText.indexOf("@removed") >= 0);
474 }
475
476 return mRemoved;
Ben Dodson920dbbb2010-08-04 15:21:06 -0700477 }
478
479 public boolean isDocOnly() {
Hui Shu5118ffe2014-02-18 14:06:42 -0800480 if (mDocOnly == null) {
481 mDocOnly = (mText != null) && (mText.indexOf("@doconly") >= 0);
Ben Dodson920dbbb2010-08-04 15:21:06 -0700482 }
Hui Shu5118ffe2014-02-18 14:06:42 -0800483 return mDocOnly;
Ben Dodson920dbbb2010-08-04 15:21:06 -0700484 }
Hui Shu5118ffe2014-02-18 14:06:42 -0800485
Ben Dodson920dbbb2010-08-04 15:21:06 -0700486 public boolean isDeprecated() {
Hui Shu5118ffe2014-02-18 14:06:42 -0800487 if (mDeprecated == null) {
488 mDeprecated = (mText != null) && (mText.indexOf("@deprecated") >= 0);
Ben Dodson920dbbb2010-08-04 15:21:06 -0700489 }
Hui Shu5118ffe2014-02-18 14:06:42 -0800490
491 return mDeprecated;
Ben Dodson920dbbb2010-08-04 15:21:06 -0700492 }
493
494 private void init() {
495 if (!mInitialized) {
496 initImpl();
497 }
498 }
499
500 private void initImpl() {
501 isHidden();
Hui Shu5118ffe2014-02-18 14:06:42 -0800502 isRemoved();
Ben Dodson920dbbb2010-08-04 15:21:06 -0700503 isDocOnly();
504 isDeprecated();
505
506 // Don't bother parsing text if we aren't generating documentation.
Ben Dodson9ccd9e32010-08-06 17:18:52 -0700507 if (Doclava.parseComments()) {
Andrew Sappersteinf959ed12011-06-23 10:24:40 -0700508 parseCommentTags(mText);
509 parseBriefTags();
Ben Dodson920dbbb2010-08-04 15:21:06 -0700510 } else {
511 // Forces methods to be recognized by findOverriddenMethods in MethodInfo.
512 mInlineTagsList.add(new TextTagInfo("Text", "Text", mText,
513 SourcePositionInfo.add(mPosition, mText, 0)));
514 }
515
516 mText = null;
517 mInitialized = true;
518
519 mInlineTags = mInlineTagsList.toArray(new TagInfo[mInlineTagsList.size()]);
520 mParamTags = mParamTagsList.toArray(new ParamTagInfo[mParamTagsList.size()]);
521 mSeeTags = mSeeTagsList.toArray(new SeeTagInfo[mSeeTagsList.size()]);
522 mThrowsTags = mThrowsTagsList.toArray(new ThrowsTagInfo[mThrowsTagsList.size()]);
523 mReturnTags =
524 ParsedTagInfo.joinTags(mReturnTagsList.toArray(new ParsedTagInfo[mReturnTagsList.size()]));
525 mDeprecatedTags =
526 ParsedTagInfo.joinTags(mDeprecatedTagsList.toArray(new ParsedTagInfo[mDeprecatedTagsList
527 .size()]));
528 mUndeprecateTags = mUndeprecateTagsList.toArray(new TagInfo[mUndeprecateTagsList.size()]);
529 mAttrTags = mAttrTagsList.toArray(new AttrTagInfo[mAttrTagsList.size()]);
530 mBriefTags = mBriefTagsList.toArray(new TagInfo[mBriefTagsList.size()]);
531
532 mParamTagsList = null;
533 mSeeTagsList = null;
534 mThrowsTagsList = null;
535 mReturnTagsList = null;
536 mDeprecatedTagsList = null;
537 mUndeprecateTagsList = null;
538 mAttrTagsList = null;
539 mBriefTagsList = null;
540 }
541
542 boolean mInitialized;
Hui Shu5118ffe2014-02-18 14:06:42 -0800543 Boolean mHidden = null;
544 Boolean mRemoved = null;
545 Boolean mDocOnly = null;
546 Boolean mDeprecated = null;
Ben Dodson920dbbb2010-08-04 15:21:06 -0700547 String mText;
548 ContainerInfo mBase;
549 SourcePositionInfo mPosition;
550 int mLine = 1;
551
552 TagInfo[] mInlineTags;
553 TagInfo[] mTags;
554 ParamTagInfo[] mParamTags;
555 SeeTagInfo[] mSeeTags;
556 ThrowsTagInfo[] mThrowsTags;
557 TagInfo[] mBriefTags;
558 TagInfo[] mReturnTags;
559 TagInfo[] mDeprecatedTags;
560 TagInfo[] mUndeprecateTags;
561 AttrTagInfo[] mAttrTags;
562
563 ArrayList<TagInfo> mInlineTagsList = new ArrayList<TagInfo>();
564 ArrayList<TagInfo> mTagsList = new ArrayList<TagInfo>();
565 ArrayList<ParamTagInfo> mParamTagsList = new ArrayList<ParamTagInfo>();
566 ArrayList<SeeTagInfo> mSeeTagsList = new ArrayList<SeeTagInfo>();
567 ArrayList<ThrowsTagInfo> mThrowsTagsList = new ArrayList<ThrowsTagInfo>();
568 ArrayList<TagInfo> mBriefTagsList = new ArrayList<TagInfo>();
569 ArrayList<ParsedTagInfo> mReturnTagsList = new ArrayList<ParsedTagInfo>();
570 ArrayList<ParsedTagInfo> mDeprecatedTagsList = new ArrayList<ParsedTagInfo>();
571 ArrayList<TagInfo> mUndeprecateTagsList = new ArrayList<TagInfo>();
572 ArrayList<AttrTagInfo> mAttrTagsList = new ArrayList<AttrTagInfo>();
573
574
575}