1 /* 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 17 package com.google.doclava; 18 19 import java.util.regex.Pattern; 20 import java.util.regex.Matcher; 21 import java.util.ArrayList; 22 import java.util.Arrays; 23 import java.util.HashSet; 24 import java.util.Set; 25 26 public class Comment { 27 static final Pattern FIRST_SENTENCE = 28 Pattern.compile("((.*?)\\.)[ \t\r\n\\<](.*)", Pattern.DOTALL); 29 30 private static final Set<String> KNOWN_TAGS = new HashSet<String>(Arrays.asList(new String[] { 31 "@apiNote", 32 "@author", 33 "@version", 34 //not used by metalava for Android docs (see @apiSince) 35 "@since", 36 //value is an Android API level (set automatically by metalava) 37 "@apiSince", 38 "@deprecated", 39 //value is an Android API level (set automatically by metalava) 40 "@deprecatedSince", 41 "@undeprecate", 42 "@docRoot", 43 "@sdkCurrent", 44 "@inheritDoc", 45 "@more", 46 "@samplecode", 47 "@sample", 48 "@include", 49 "@serial", 50 "@implNote", 51 "@implSpec", 52 "@usesMathJax", 53 })); 54 Comment(String text, ContainerInfo base, SourcePositionInfo sp)55 public Comment(String text, ContainerInfo base, SourcePositionInfo sp) { 56 mText = text; 57 mBase = base; 58 // sp now points to the end of the text, not the beginning! 59 mPosition = SourcePositionInfo.findBeginning(sp, text); 60 } 61 parseCommentTags(String text)62 private void parseCommentTags(String text) { 63 int i = 0; 64 int length = text.length(); 65 while (i < length && isWhitespaceChar(text.charAt(i++))) {} 66 67 if (i <= 0) { 68 return; 69 } 70 71 text = text.substring(i-1); 72 length = text.length(); 73 74 if ("".equals(text)) { 75 return; 76 } 77 78 int start = 0; 79 int end = findStartOfBlock(text, start); 80 81 82 // possible scenarios 83 // main and block(s) 84 // main only (end == -1) 85 // block(s) only (end == 0) 86 87 switch (end) { 88 case -1: // main only 89 parseMainDescription(text, start, length); 90 return; 91 case 0: // block(s) only 92 break; 93 default: // main and block 94 95 // find end of main because end is really the beginning of @ 96 parseMainDescription(text, start, findEndOfMainOrBlock(text, start, end)); 97 break; 98 } 99 100 // parse blocks 101 for (start = end; start < length; start = end) { 102 end = findStartOfBlock(text, start+1); 103 104 if (end == -1) { 105 parseBlock(text, start, length); 106 break; 107 } else { 108 parseBlock(text, start, findEndOfMainOrBlock(text, start, end)); 109 } 110 } 111 112 // for each block 113 // make block parts 114 // end is either next @ at beginning of line or end of text 115 } 116 findEndOfMainOrBlock(String text, int start, int end)117 private int findEndOfMainOrBlock(String text, int start, int end) { 118 for (int i = end-1; i >= start; i--) { 119 if (!isWhitespaceChar(text.charAt(i))) { 120 end = i+1; 121 break; 122 } 123 } 124 return end; 125 } 126 parseMainDescription(String mainDescription, int start, int end)127 private void parseMainDescription(String mainDescription, int start, int end) { 128 if (mainDescription == null) { 129 return; 130 } 131 132 SourcePositionInfo pos = SourcePositionInfo.add(mPosition, mText, 0); 133 while (start < end) { 134 int startOfInlineTag = findStartIndexOfInlineTag(mainDescription, start, end); 135 136 // if there are no more tags 137 if (startOfInlineTag == -1) { 138 tag(null, mainDescription.substring(start, end), true, pos); 139 return; 140 } 141 142 //int endOfInlineTag = mainDescription.indexOf('}', startOfInlineTag); 143 int endOfInlineTag = findEndIndexOfInlineTag(mainDescription, startOfInlineTag, end); 144 145 // if there was only beginning tag 146 if (endOfInlineTag == -1) { 147 // parse all of main as one tag 148 tag(null, mainDescription.substring(start, end), true, pos); 149 return; 150 } 151 152 endOfInlineTag++; // add one to make it a proper ending index 153 154 // do first part without an inline tag - ie, just plaintext 155 tag(null, mainDescription.substring(start, startOfInlineTag), true, pos); 156 157 // parse the rest of this section, the inline tag 158 parseInlineTag(mainDescription, startOfInlineTag, endOfInlineTag, pos); 159 160 // keep going 161 start = endOfInlineTag; 162 } 163 } 164 findStartIndexOfInlineTag(String text, int fromIndex, int toIndex)165 private int findStartIndexOfInlineTag(String text, int fromIndex, int toIndex) { 166 for (int i = fromIndex; i < (toIndex-3); i++) { 167 if (text.charAt(i) == '{' && text.charAt(i+1) == '@' && !isWhitespaceChar(text.charAt(i+2))) { 168 return i; 169 } 170 } 171 172 return -1; 173 } 174 findEndIndexOfInlineTag(String text, int fromIndex, int toIndex)175 private int findEndIndexOfInlineTag(String text, int fromIndex, int toIndex) { 176 int braceDepth = 0; 177 for (int i = fromIndex; i < toIndex; i++) { 178 if (text.charAt(i) == '{') { 179 braceDepth++; 180 } else if (text.charAt(i) == '}') { 181 braceDepth--; 182 if (braceDepth == 0) { 183 return i; 184 } 185 } 186 } 187 188 return -1; 189 } 190 parseInlineTag(String text, int start, int end, SourcePositionInfo pos)191 private void parseInlineTag(String text, int start, int end, SourcePositionInfo pos) { 192 int index = start+1; 193 //int len = text.length(); 194 char c = text.charAt(index); 195 // find the end of the tag name "@something" 196 // need to do something special if we have '}' 197 while (index < end && !isWhitespaceChar(c)) { 198 199 // if this tag has no value, just return with tag name only 200 if (c == '}') { 201 // TODO - should value be "" or null? 202 tag(text.substring(start+1, end), null, true, pos); 203 return; 204 } 205 c = text.charAt(index++); 206 } 207 208 // don't parse things that don't have at least one extra character after @ 209 // probably should be plus 3 210 // TODO - remove this - think it's fixed by change in parseMainDescription 211 if (index == start+3) { 212 return; 213 } 214 215 int endOfFirstPart = index-1; 216 217 // get to beginning of tag value 218 while (index < end && isWhitespaceChar(text.charAt(index++))) {} 219 int startOfSecondPart = index-1; 220 221 // +1 to get rid of opening brace and -1 to get rid of closing brace 222 // maybe i wanna make this more elegant 223 String tagName = text.substring(start+1, endOfFirstPart); 224 String tagText = text.substring(startOfSecondPart, end-1); 225 tag(tagName, tagText, true, pos); 226 } 227 228 229 /** 230 * Finds the index of the start of a new block comment or -1 if there are 231 * no more starts. 232 * @param text The String to search 233 * @param start the index of the String to start searching 234 * @return The index of the start of a new block comment or -1 if there are 235 * no more starts. 236 */ findStartOfBlock(String text, int start)237 private int findStartOfBlock(String text, int start) { 238 // how to detect we're at a new @ 239 // if the chars to the left of it are \r or \n, we're at one 240 // if the chars to the left of it are ' ' or \t, keep looking 241 // otherwise, we're in the middle of a block, keep looking 242 int index = text.indexOf('@', start); 243 244 // no @ in text or index at first position 245 if (index == -1 || 246 (index == 0 && text.length() > 1 && !isWhitespaceChar(text.charAt(index+1)))) { 247 return index; 248 } 249 250 index = getPossibleStartOfBlock(text, index); 251 252 int i = index-1; // start at the character immediately to the left of @ 253 char c; 254 while (i >= 0) { 255 c = text.charAt(i--); 256 257 // found a new block comment because we're at the beginning of a line 258 if (c == '\r' || c == '\n') { 259 return index; 260 } 261 262 // there is a non whitespace character to the left of the @ 263 // before finding a new line, keep searching 264 if (c != ' ' && c != '\t') { 265 index = getPossibleStartOfBlock(text, index+1); 266 i = index-1; 267 } 268 269 // some whitespace character, so keep looking, we might be at a new block comment 270 } 271 272 return -1; 273 } 274 getPossibleStartOfBlock(String text, int index)275 private int getPossibleStartOfBlock(String text, int index) { 276 while (isWhitespaceChar(text.charAt(index+1)) || !isWhitespaceChar(text.charAt(index-1))) { 277 index = text.indexOf('@', index+1); 278 279 if (index == -1 || index == text.length()-1) { 280 return -1; 281 } 282 } 283 284 return index; 285 } 286 parseBlock(String text, int startOfBlock, int endOfBlock)287 private void parseBlock(String text, int startOfBlock, int endOfBlock) { 288 SourcePositionInfo pos = SourcePositionInfo.add(mPosition, mText, startOfBlock); 289 int index = startOfBlock; 290 291 for (char c = text.charAt(index); 292 index < endOfBlock && !isWhitespaceChar(c); c = text.charAt(index++)) {} 293 if (index == startOfBlock+1) { 294 return; 295 } 296 297 int endOfFirstPart = index-1; 298 if (index == endOfBlock) { 299 // TODO - should value be null or "" 300 tag(text.substring(startOfBlock, 301 findEndOfMainOrBlock(text, startOfBlock, index)), "", false, pos); 302 return; 303 } 304 305 306 // get to beginning of tag value 307 while (index < endOfBlock && isWhitespaceChar(text.charAt(index++))) {} 308 int startOfSecondPart = index-1; 309 310 tag(text.substring(startOfBlock, endOfFirstPart), 311 text.substring(startOfSecondPart, endOfBlock), false, pos); 312 } 313 isWhitespaceChar(char c)314 private boolean isWhitespaceChar(char c) { 315 switch (c) { 316 case ' ': 317 case '\r': 318 case '\t': 319 case '\n': 320 return true; 321 } 322 return false; 323 } 324 tag(String name, String text, boolean isInline, SourcePositionInfo pos)325 private void tag(String name, String text, boolean isInline, SourcePositionInfo pos) { 326 /* 327 * String s = isInline ? "inline" : "outofline"; System.out.println("---> " + s + " name=[" + 328 * name + "] text=[" + text + "]"); 329 */ 330 if (name == null) { 331 mInlineTagsList.add(new TextTagInfo("Text", "Text", text, pos)); 332 } else if (name.equals("@param")) { 333 mParamTagsList.add(new ParamTagInfo("@param", "@param", text, mBase, pos)); 334 } else if (name.equals("@apiSince")) { 335 setApiSince(text); 336 } else if (name.equals("@deprecatedSince")) { 337 setDeprecatedSince(text); 338 } else if (name.equals("@see")) { 339 mSeeTagsList.add(new SeeTagInfo("@see", "@see", text, mBase, pos)); 340 } else if (name.equals("@link")) { 341 if (Doclava.DEVSITE_IGNORE_JDLINKS) { 342 TagInfo linkTag = new TextTagInfo(name, name, text, pos); 343 mInlineTagsList.add(linkTag); 344 } else { 345 mInlineTagsList.add(new SeeTagInfo(name, "@see", text, mBase, pos)); 346 } 347 } else if (name.equals("@linkplain")) { 348 mInlineTagsList.add(new SeeTagInfo(name, "@linkplain", text, mBase, pos)); 349 } else if (name.equals("@value")) { 350 mInlineTagsList.add(new SeeTagInfo(name, "@value", text, mBase, pos)); 351 } else if (name.equals("@throws") || name.equals("@exception")) { 352 mThrowsTagsList.add(new ThrowsTagInfo("@throws", "@throws", text, mBase, pos)); 353 } else if (name.equals("@return")) { 354 mReturnTagsList.add(new ParsedTagInfo("@return", "@return", text, mBase, pos)); 355 } else if (name.equals("@deprecated")) { 356 if (text.length() == 0) { 357 Errors.error(Errors.MISSING_COMMENT, pos, "@deprecated tag with no explanatory comment"); 358 text = "No replacement."; 359 } 360 mDeprecatedTagsList.add(new ParsedTagInfo("@deprecated", "@deprecated", text, mBase, pos)); 361 } else if (name.equals("@literal")) { 362 mInlineTagsList.add(new LiteralTagInfo(text, pos)); 363 } else if (name.equals("@code")) { 364 mInlineTagsList.add(new CodeTagInfo(text, pos)); 365 } else if (name.equals("@hide") || name.equals("@removed") 366 || name.equals("@pending") || name.equals("@doconly")) { 367 // nothing 368 } else if (name.equals("@attr")) { 369 AttrTagInfo tag = new AttrTagInfo("@attr", "@attr", text, mBase, pos); 370 mAttrTagsList.add(tag); 371 Comment c = tag.description(); 372 if (c != null) { 373 for (TagInfo t : c.tags()) { 374 mInlineTagsList.add(t); 375 } 376 } 377 } else if (name.equals("@undeprecate")) { 378 mUndeprecateTagsList.add(new TextTagInfo("@undeprecate", "@undeprecate", text, pos)); 379 } else if (name.equals("@include") || name.equals("@sample")) { 380 mInlineTagsList.add(new SampleTagInfo(name, "@include", text, mBase, pos)); 381 } else if (name.equals("@apiNote") || name.equals("@implSpec") || name.equals("@implNote")) { 382 mTagsList.add(new ParsedTagInfo(name, name, text, mBase, pos)); 383 } else if (name.equals("@memberDoc")) { 384 mMemberDocTagsList.add(new ParsedTagInfo("@memberDoc", "@memberDoc", text, mBase, pos)); 385 } else if (name.equals("@paramDoc")) { 386 mParamDocTagsList.add(new ParsedTagInfo("@paramDoc", "@paramDoc", text, mBase, pos)); 387 } else if (name.equals("@returnDoc")) { 388 mReturnDocTagsList.add(new ParsedTagInfo("@returnDoc", "@returnDoc", text, mBase, pos)); 389 } else { 390 boolean known = KNOWN_TAGS.contains(name); 391 if (!known) { 392 known = Doclava.knownTags.contains(name); 393 } 394 if (!known) { 395 if (name.length() >= 2 && Character.isUpperCase(name.charAt(1))) { 396 // This is a workaround for b/135928616 where parsing of comments fails when there is 397 // a Java annotation and not a tag. 398 Errors.error(Errors.JAVA_TAG_IN_COMMENT, 399 pos == null ? null : new SourcePositionInfo(pos), 400 "Invalid tag: " + name); 401 } else { 402 Errors.error(Errors.UNKNOWN_TAG, pos == null ? null : new SourcePositionInfo(pos), 403 "Unknown tag: " + name); 404 } 405 } 406 TagInfo t = new TextTagInfo(name, name, text, pos); 407 if (isInline) { 408 mInlineTagsList.add(t); 409 } else { 410 mTagsList.add(t); 411 } 412 } 413 } 414 parseBriefTags()415 private void parseBriefTags() { 416 int N = mInlineTagsList.size(); 417 418 // look for "@more" tag, which means that we might go past the first sentence. 419 int more = -1; 420 for (int i = 0; i < N; i++) { 421 if (mInlineTagsList.get(i).name().equals("@more")) { 422 more = i; 423 } 424 } 425 if (more >= 0) { 426 for (int i = 0; i < more; i++) { 427 mBriefTagsList.add(mInlineTagsList.get(i)); 428 } 429 } else { 430 for (int i = 0; i < N; i++) { 431 TagInfo t = mInlineTagsList.get(i); 432 if (t.name().equals("Text")) { 433 Matcher m = FIRST_SENTENCE.matcher(t.text()); 434 if (m.matches()) { 435 String text = m.group(1); 436 TagInfo firstSentenceTag = new TagInfo(t.name(), t.kind(), text, t.position()); 437 mBriefTagsList.add(firstSentenceTag); 438 break; 439 } 440 } 441 mBriefTagsList.add(t); 442 443 } 444 } 445 } 446 tags()447 public TagInfo[] tags() { 448 init(); 449 return mInlineTags; 450 } 451 tags(String name)452 public TagInfo[] tags(String name) { 453 init(); 454 ArrayList<TagInfo> results = new ArrayList<TagInfo>(); 455 int N = mInlineTagsList.size(); 456 for (int i = 0; i < N; i++) { 457 TagInfo t = mInlineTagsList.get(i); 458 if (t.name().equals(name)) { 459 results.add(t); 460 } 461 } 462 return results.toArray(TagInfo.getArray(results.size())); 463 } 464 blockTags()465 public TagInfo[] blockTags() { 466 init(); 467 return mTags; 468 } 469 paramTags()470 public ParamTagInfo[] paramTags() { 471 init(); 472 return mParamTags; 473 } 474 seeTags()475 public SeeTagInfo[] seeTags() { 476 init(); 477 return mSeeTags; 478 } 479 throwsTags()480 public ThrowsTagInfo[] throwsTags() { 481 init(); 482 return mThrowsTags; 483 } 484 returnTags()485 public TagInfo[] returnTags() { 486 init(); 487 return mReturnTags; 488 } 489 deprecatedTags()490 public TagInfo[] deprecatedTags() { 491 init(); 492 return mDeprecatedTags; 493 } 494 undeprecateTags()495 public TagInfo[] undeprecateTags() { 496 init(); 497 return mUndeprecateTags; 498 } 499 attrTags()500 public AttrTagInfo[] attrTags() { 501 init(); 502 return mAttrTags; 503 } 504 briefTags()505 public TagInfo[] briefTags() { 506 init(); 507 return mBriefTags; 508 } 509 memberDocTags()510 public ParsedTagInfo[] memberDocTags() { 511 init(); 512 return mMemberDocTags; 513 } 514 paramDocTags()515 public ParsedTagInfo[] paramDocTags() { 516 init(); 517 return mParamDocTags; 518 } 519 returnDocTags()520 public ParsedTagInfo[] returnDocTags() { 521 init(); 522 return mReturnDocTags; 523 } 524 isHidden()525 public boolean isHidden() { 526 if (mHidden == null) { 527 mHidden = !Doclava.checkLevel(Doclava.SHOW_HIDDEN) && 528 (mText != null) && (mText.indexOf("@hide") >= 0 || mText.indexOf("@pending") >= 0); 529 } 530 return mHidden; 531 } 532 isRemoved()533 public boolean isRemoved() { 534 if (mRemoved == null) { 535 mRemoved = !Doclava.checkLevel(Doclava.SHOW_HIDDEN) && 536 (mText != null) && (mText.indexOf("@removed") >= 0); 537 } 538 539 return mRemoved; 540 } 541 setDeprecatedSince(String since)542 public void setDeprecatedSince(String since) { 543 if (since != null) { 544 since = since.trim(); 545 } 546 mDeprecatedSince = since; 547 } 548 getDeprecatedSince()549 public String getDeprecatedSince() { 550 return mDeprecatedSince; 551 } 552 setApiSince(String since)553 public void setApiSince(String since) { 554 if (since != null) { 555 since = since.trim(); 556 } 557 mApiSince = since; 558 } 559 getApiSince()560 public String getApiSince() { 561 //return the value of @apiSince, an API level in Android 562 return mApiSince; 563 } 564 isDocOnly()565 public boolean isDocOnly() { 566 if (mDocOnly == null) { 567 mDocOnly = (mText != null) && (mText.indexOf("@doconly") >= 0); 568 } 569 return mDocOnly; 570 } 571 isDeprecated()572 public boolean isDeprecated() { 573 if (mDeprecated == null) { 574 mDeprecated = (mText != null) && (mText.indexOf("@deprecated") >= 0); 575 } 576 577 return mDeprecated; 578 } 579 init()580 private void init() { 581 if (!mInitialized) { 582 initImpl(); 583 } 584 } 585 initImpl()586 private void initImpl() { 587 isHidden(); 588 isRemoved(); 589 isDocOnly(); 590 isDeprecated(); 591 592 // Don't bother parsing text if we aren't generating documentation. 593 if (Doclava.parseComments()) { 594 parseCommentTags(mText); 595 parseBriefTags(); 596 } else { 597 // Forces methods to be recognized by findOverriddenMethods in MethodInfo. 598 mInlineTagsList.add(new TextTagInfo("Text", "Text", mText, 599 SourcePositionInfo.add(mPosition, mText, 0))); 600 } 601 602 mText = null; 603 mInitialized = true; 604 605 mInlineTags = mInlineTagsList.toArray(TagInfo.getArray(mInlineTagsList.size())); 606 mTags = mTagsList.toArray(TagInfo.getArray(mTagsList.size())); 607 mParamTags = mParamTagsList.toArray(ParamTagInfo.getArray(mParamTagsList.size())); 608 mSeeTags = mSeeTagsList.toArray(SeeTagInfo.getArray(mSeeTagsList.size())); 609 mThrowsTags = mThrowsTagsList.toArray(ThrowsTagInfo.getArray(mThrowsTagsList.size())); 610 mReturnTags = ParsedTagInfo.joinTags( 611 mReturnTagsList.toArray(ParsedTagInfo.getArray(mReturnTagsList.size()))); 612 mDeprecatedTags = ParsedTagInfo.joinTags( 613 mDeprecatedTagsList.toArray(ParsedTagInfo.getArray(mDeprecatedTagsList.size()))); 614 mUndeprecateTags = mUndeprecateTagsList.toArray(TagInfo.getArray(mUndeprecateTagsList.size())); 615 mAttrTags = mAttrTagsList.toArray(AttrTagInfo.getArray(mAttrTagsList.size())); 616 mBriefTags = mBriefTagsList.toArray(TagInfo.getArray(mBriefTagsList.size())); 617 mMemberDocTags = mMemberDocTagsList.toArray(ParsedTagInfo.getArray(mMemberDocTagsList.size())); 618 mParamDocTags = mParamDocTagsList.toArray(ParsedTagInfo.getArray(mParamDocTagsList.size())); 619 mReturnDocTags = mReturnDocTagsList.toArray(ParsedTagInfo.getArray(mReturnDocTagsList.size())); 620 621 mTagsList = null; 622 mParamTagsList = null; 623 mSeeTagsList = null; 624 mThrowsTagsList = null; 625 mReturnTagsList = null; 626 mDeprecatedTagsList = null; 627 mUndeprecateTagsList = null; 628 mAttrTagsList = null; 629 mBriefTagsList = null; 630 mMemberDocTagsList = null; 631 mParamDocTagsList = null; 632 mReturnDocTagsList = null; 633 } 634 635 boolean mInitialized; 636 Boolean mHidden = null; 637 Boolean mRemoved = null; 638 Boolean mDocOnly = null; 639 Boolean mDeprecated = null; 640 String mDeprecatedSince; 641 String mApiSince; 642 String mText; 643 ContainerInfo mBase; 644 SourcePositionInfo mPosition; 645 int mLine = 1; 646 647 TagInfo[] mInlineTags; 648 TagInfo[] mTags; 649 ParamTagInfo[] mParamTags; 650 SeeTagInfo[] mSeeTags; 651 ThrowsTagInfo[] mThrowsTags; 652 TagInfo[] mBriefTags; 653 TagInfo[] mReturnTags; 654 TagInfo[] mDeprecatedTags; 655 TagInfo[] mUndeprecateTags; 656 AttrTagInfo[] mAttrTags; 657 ParsedTagInfo[] mMemberDocTags; 658 ParsedTagInfo[] mParamDocTags; 659 ParsedTagInfo[] mReturnDocTags; 660 661 ArrayList<TagInfo> mInlineTagsList = new ArrayList<TagInfo>(); 662 ArrayList<TagInfo> mTagsList = new ArrayList<TagInfo>(); 663 ArrayList<ParamTagInfo> mParamTagsList = new ArrayList<ParamTagInfo>(); 664 ArrayList<SeeTagInfo> mSeeTagsList = new ArrayList<SeeTagInfo>(); 665 ArrayList<ThrowsTagInfo> mThrowsTagsList = new ArrayList<ThrowsTagInfo>(); 666 ArrayList<TagInfo> mBriefTagsList = new ArrayList<TagInfo>(); 667 ArrayList<ParsedTagInfo> mReturnTagsList = new ArrayList<ParsedTagInfo>(); 668 ArrayList<ParsedTagInfo> mDeprecatedTagsList = new ArrayList<ParsedTagInfo>(); 669 ArrayList<TagInfo> mUndeprecateTagsList = new ArrayList<TagInfo>(); 670 ArrayList<AttrTagInfo> mAttrTagsList = new ArrayList<AttrTagInfo>(); 671 ArrayList<ParsedTagInfo> mMemberDocTagsList = new ArrayList<ParsedTagInfo>(); 672 ArrayList<ParsedTagInfo> mParamDocTagsList = new ArrayList<ParsedTagInfo>(); 673 ArrayList<ParsedTagInfo> mReturnDocTagsList = new ArrayList<ParsedTagInfo>(); 674 675 } 676