1 /* gnu.classpath.tools.gjdoc.DocImpl 2 Copyright (C) 2001, 2012 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 19 02111-1307 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 package gnu.classpath.tools.gjdoc; 39 40 import com.sun.javadoc.*; 41 import java.util.*; 42 import java.text.*; 43 import java.io.File; 44 import javax.swing.text.Segment; 45 46 /** 47 * Represents the least common denominator of all Javadoc 48 * comment classes. 49 */ 50 public abstract class DocImpl implements Doc, TagContainer { 51 52 protected static Tag[] seeTagEmptyArr = new SeeTagImpl[0]; 53 protected static Tag[] linkTagEmptyArr = new LinkTagImpl[0]; 54 protected static Tag[] paramTagEmptyArr = new ParamTagImpl[0]; 55 protected static Tag[] throwsTagEmptyArr = new ThrowsTagImpl[0]; 56 protected SourcePosition position; 57 private String boilerplateComment; 58 59 // Return the text of the comment for this doc item. commentText()60 public String commentText() { 61 62 StringBuffer rc=new StringBuffer(); 63 64 Tag[] textTags=(Tag[])tagMap.get("text"); 65 if (textTags!=null) { 66 for (int i=0; i<textTags.length; ++i) { 67 rc.append(textTags[i].text()); 68 } 69 } 70 return rc.toString(); 71 } 72 73 // Compares this Object with the specified Object for order. compareTo(Doc d)74 public int compareTo(Doc d) { 75 return Main.getInstance().getCollator().compare(name(), d.name()); 76 } 77 78 // Return the first sentence of the comment as tags. firstSentenceTags()79 public Tag[] firstSentenceTags() { 80 81 Tag[] rc=(Tag[])tagMap.get("first"); 82 if (rc==null) rc=new Tag[0]; 83 return rc; 84 } 85 86 // Return the full unprocessed text of the comment. getRawCommentText()87 public String getRawCommentText() { 88 if (rawDocumentation!=null) 89 return rawDocumentation; 90 else if (rawDocOffset>=0) 91 return Main.getRootDoc().readRawComment(rawDocOffset); 92 else 93 return null; 94 } 95 96 // Return comment as tags. inlineTags()97 public Tag[] inlineTags() { 98 99 Tag[] rc=(Tag[])tagMap.get("inline"); 100 if (rc==null) rc=new Tag[0]; 101 return rc; 102 } 103 104 // Is this Doc item a class. isClass()105 public boolean isClass() { 106 return false; 107 } 108 109 // Is this Doc item a constructor? False until overridden. isConstructor()110 public boolean isConstructor() { 111 return false; 112 } 113 114 // Is this Doc item a error class? False until overridden. isError()115 public boolean isError() { 116 return false; 117 } 118 119 // Is this Doc item a exception class? False until overridden. isException()120 public boolean isException() { 121 return false; 122 } 123 124 // Is this Doc item a field? False until overridden. isField()125 public boolean isField() { 126 return false; 127 } 128 129 // return true if this Doc is include in the active set. isIncluded()130 public boolean isIncluded() { 131 return false; 132 } 133 134 // Is this Doc item a interface? False until overridden. isInterface()135 public boolean isInterface() { 136 return false; 137 } 138 139 // Is this Doc item a simple method (i.e. isMethod()140 public boolean isMethod() { 141 return false; 142 } 143 isPackage()144 public boolean isPackage() { 145 return false; 146 } 147 148 // Is this Doc item a ordinary class (i.e. isOrdinaryClass()149 public boolean isOrdinaryClass() { 150 return false; 151 } 152 153 // Return the see also tags in this Doc item. seeTags()154 public SeeTag[] seeTags() { 155 return (SeeTag[])getTagArr("see", seeTagEmptyArr); 156 } 157 getTagArr(String kindOfTag, Tag[] defaultRc)158 protected Tag[] getTagArr(String kindOfTag, Tag[] defaultRc) { 159 Tag[] rc=(Tag[])tagMap.get(kindOfTag); 160 if (rc==null) rc=defaultRc; 161 return rc; 162 } 163 164 // Set the full unprocessed text of the comment. setRawCommentText(String rawDocumentation)165 public void setRawCommentText(String rawDocumentation) { 166 this.rawDocumentation=rawDocumentation; 167 } 168 resolveComments()169 public void resolveComments() { 170 171 if (rawDocumentation!=null && tagMap.isEmpty()) { 172 char[] charArray = rawDocumentation.toCharArray(); 173 int length = rawDocumentation.length(); 174 int startOffset = 0; 175 int endOffset = 0; 176 if (charArray[0] == '/' 177 && charArray[1] == '*' 178 && charArray[2] == '*' 179 && charArray[length - 2] == '*' 180 && charArray[length - 1] == '/') { 181 182 startOffset = 3; 183 endOffset = 2; 184 } 185 186 this.tagMap=parseCommentTags(charArray, 187 startOffset, 188 length - endOffset, 189 getContextClass(), 190 getContextMember(), 191 null, 192 boilerplateComment); 193 194 if (Main.getInstance().isCacheRawComments()) { 195 rawDocOffset=Main.getRootDoc().writeRawComment(rawDocumentation); 196 rawDocumentation=null; 197 } 198 199 resolveTags(); 200 } 201 else if (tagMap.isEmpty() && null != boilerplateComment) { 202 tagMap.put("all", new Tag[] { new TagImpl("@boilerplate", boilerplateComment,getContextClass(),null) }); 203 tagMap.put("@boilerplate", new Tag[] { new TagImpl("@boilerplate", boilerplateComment,getContextClass(),null) }); 204 } 205 } 206 skipHtmlWhitespace(char[] buffer, int startIndex)207 public static int skipHtmlWhitespace(char[] buffer, int startIndex) { 208 while (startIndex < buffer.length) { 209 char c=buffer[startIndex]; 210 if (!Parser.isWhitespace(c)) { 211 break; 212 } 213 else { 214 ++ startIndex; 215 } 216 } 217 return startIndex; 218 } 219 220 /** 221 * Looks for an end-of-sentence marker in <code>text</code>, 222 * starting at <code>startIndex</code> and stopping at 223 * <code>endIndex</code>. 224 * 225 * @param text the text to be searched 226 * @param startIndex index in <code>text</code> at which to start 227 * @param endIndex index in <code>text</code> at which to stop 228 * 229 * @return the index of the character following the end-of-sentence 230 * marker, <code>endIndex</code> if no end-of-sentence 231 * marker could be found, or -1 if not implemented. 232 */ findEndOfSentence(char[] text, int startIndex, int endIndex)233 private static int findEndOfSentence(char[] text, int startIndex, 234 int endIndex) 235 { 236 if (Main.getInstance().isUseBreakIterator()) { 237 Segment segment = new Segment(text, startIndex, endIndex - startIndex); 238 BreakIterator breakIterator = BreakIterator.getSentenceInstance(Main.getInstance().getLocale()); 239 breakIterator.setText(segment); 240 int result = breakIterator.next(); 241 if (BreakIterator.DONE == result) { 242 return endIndex; 243 } 244 else { 245 return result; 246 } 247 } 248 else { 249 while (startIndex < endIndex) { 250 if (text[startIndex] == '.' 251 && (startIndex+1 == endIndex 252 || Character.isWhitespace(text[startIndex+1]) 253 || isHTMLBreakTag(text, startIndex+1, endIndex) 254 )) { 255 return startIndex; 256 } 257 258 startIndex++; 259 } 260 return endIndex; 261 } 262 } 263 264 /** 265 * Returns true is the text from start to end begins with a 'p' or 'br' tag. 266 */ isHTMLBreakTag(char[] text, int start, int end)267 private static boolean isHTMLBreakTag(char[] text, int start, int end) 268 { 269 String[] breakTags = { 270 "p>", "/p>", "h1>", "h2>", "h3>", "h4>", "h5>", "h6>", "hr>", 271 "pre>", "/pre>" 272 }; 273 274 if (text[start] == '<') { 275 276 outer: 277 for (int i=0; i<breakTags.length; ++i) { 278 String tag = breakTags[i]; 279 int len = tag.length(); 280 if (start + len < end) { 281 for (int j=0; j<len; ++j) { 282 char c = tag.charAt(j); 283 if (Character.toLowerCase(text[start + 1 + j]) != c) { 284 continue outer; 285 } 286 } 287 return true; 288 } 289 } 290 } 291 return false; 292 } 293 294 //private static final StringBuffer buf=new StringBuffer(32768); 295 private static final StringBuffer whitespaceBuf=new StringBuffer(); 296 private static char[] charBuf = new char[60000]; 297 private static int bufPos = 0; 298 appendToBuf(char c)299 private static void appendToBuf(char c) 300 { 301 if (bufPos < charBuf.length) { 302 charBuf[bufPos++] = c; 303 } 304 else { 305 // 306 } 307 } 308 appendToBuf(StringBuffer s)309 private static void appendToBuf(StringBuffer s) 310 { 311 if (bufPos + s.length() <= charBuf.length) { 312 s.getChars(0, s.length(), charBuf, bufPos); 313 bufPos += s.length(); 314 } 315 else { 316 // 317 } 318 } 319 setBufLength(int length)320 private static void setBufLength(int length) 321 { 322 bufPos = 0; 323 } 324 bufToString()325 private static String bufToString() 326 { 327 return new String(charBuf, 0, bufPos); 328 } 329 bufLength()330 private static int bufLength() 331 { 332 return bufPos; 333 } 334 parseCommentTags(char[] comment, int startIndex, int endIndex, ClassDocImpl contextClass, MemberDocImpl contextMember, AbstractTagImpl contextTag, String boilerplateComment)335 public static Map parseCommentTags(char[] comment, int startIndex, int endIndex, 336 ClassDocImpl contextClass, MemberDocImpl contextMember, 337 AbstractTagImpl contextTag, String boilerplateComment) { 338 339 int rawDocStart=skipHtmlWhitespace(comment, startIndex); 340 341 int firstSentenceEnd = 0; 342 343 if (comment.length>rawDocStart) { 344 345 firstSentenceEnd = findEndOfSentence(comment, rawDocStart, comment.length); 346 347 if (firstSentenceEnd < 0) { 348 BreakIterator boundary = BreakIterator.getSentenceInstance(Locale.ENGLISH); 349 boundary.setText(new ArrayCharacterIterator(comment, rawDocStart)); 350 boundary.first(); 351 boundary.next(); 352 firstSentenceEnd = boundary.current(); 353 } 354 355 // Always include period at end of sentence if there is one. 356 if (firstSentenceEnd < comment.length 357 && '.' == comment[firstSentenceEnd]) { 358 ++ firstSentenceEnd; 359 } 360 } 361 362 final int STATE_BEGOFLINE = 1; 363 final int STATE_TEXT = 2; 364 final int STATE_PARAM = 3; 365 final int STATE_PARAMVALUE = 4; 366 final int STATE_PARAMWRAP = 5; 367 final int STATE_INLINEPARAM = 6; 368 final int STATE_INLINEPARAMVALUE = 7; 369 final int STATE_WHITESPACE = 8; 370 final int STATE_INLINEPARAMVALUE_BOL = 9; 371 final int STATE_IPV_WHITESPACE = 10; 372 373 int state=STATE_BEGOFLINE; 374 int prevState=STATE_TEXT; 375 376 setBufLength(0); 377 whitespaceBuf.setLength(0); 378 379 String paramName="", paramValue=""; 380 381 Map tags=new HashMap(); 382 tags.put("inline", new LinkedList()); 383 tags.put("first", new LinkedList()); 384 tags.put("all", new LinkedList()); 385 386 final char EOL=(char)-1; 387 388 for (int i=rawDocStart; i<=endIndex; ++i) { 389 char c=(i<endIndex)?comment[i]:EOL; 390 char peek=(i<endIndex-1)?comment[i+1]:EOL; 391 392 switch (state){ 393 394 case STATE_BEGOFLINE: 395 if (i==firstSentenceEnd) { 396 AbstractTagImpl newTag = addTag(tags, "text", bufToString(), true, contextClass, contextMember, contextTag, false); 397 if (null != newTag) { 398 contextTag = newTag; 399 } 400 setBufLength(0); 401 } 402 403 if (Parser.isWhitespace(c)) { 404 // ignore 405 } 406 else if (c=='*') { 407 // ignore, but go to STATE_TEXT 408 if (peek!='*' && peek!='@' && peek!=EOL) { 409 state=STATE_WHITESPACE; 410 } 411 } 412 else if (c=='@' || (c=='{' && peek=='@') || c==EOL) { 413 if (bufLength()>0) { 414 addTag(tags, "text", bufToString(), i<firstSentenceEnd, contextClass, contextMember, contextTag, false); 415 setBufLength(0); 416 } 417 if (c=='{') { 418 ++i; 419 state=STATE_INLINEPARAM; 420 } 421 else { 422 state=STATE_PARAM; 423 } 424 } 425 else { 426 state=STATE_TEXT; 427 appendToBuf(whitespaceBuf); 428 whitespaceBuf.setLength(0); 429 appendToBuf(c); 430 } 431 break; 432 433 case STATE_WHITESPACE: 434 if (i==firstSentenceEnd) { 435 AbstractTagImpl newTag = addTag(tags, "text", bufToString(), true, contextClass, contextMember, contextTag, false); 436 if (null != newTag) { 437 contextTag = newTag; 438 } 439 setBufLength(0); 440 } 441 442 if (c=='\n') { 443 whitespaceBuf.append(c); 444 state=STATE_BEGOFLINE; 445 } 446 else if (Parser.isWhitespace(c)) { 447 whitespaceBuf.append(c); 448 } 449 else if (c=='@' || (c=='{' && peek=='@') || c==EOL) { 450 if (bufLength()>0) { 451 AbstractTagImpl newTag = addTag(tags, "text", bufToString(), i<firstSentenceEnd, contextClass, contextMember, contextTag, false); 452 if (null != newTag) { 453 contextTag = newTag; 454 } 455 setBufLength(0); 456 } 457 if (c=='{') { 458 ++i; 459 state=STATE_INLINEPARAM; 460 } 461 else { 462 state=STATE_PARAM; 463 } 464 } 465 else { 466 appendToBuf(whitespaceBuf); 467 whitespaceBuf.setLength(0); 468 appendToBuf(c); 469 state=STATE_TEXT; 470 } 471 break; 472 473 case STATE_PARAMWRAP: 474 if (c=='\n') { 475 appendToBuf(c); 476 } 477 else if (Parser.isWhitespace(c)) { 478 // ignore 479 } 480 else if (c=='*') { 481 // ignore, but go to STATE_TEXT 482 /* 483 if (i<endIndex && comment[i+1]!='*' && comment[i+1]!='@') { 484 state=STATE_PARAMVALUE; 485 } 486 */ 487 } 488 else if (c=='@' || c==EOL) { 489 paramValue=bufToString(); 490 AbstractTagImpl newTag = addTag(tags, paramName, paramValue, i<firstSentenceEnd, contextClass, contextMember, contextTag, false); 491 if (null != newTag) { 492 contextTag = newTag; 493 } 494 setBufLength(0); 495 if (c=='{') { 496 ++i; 497 state=STATE_INLINEPARAM; 498 } 499 else { 500 state=STATE_PARAM; 501 } 502 } 503 else { 504 state=STATE_PARAMVALUE; 505 appendToBuf(c); 506 } 507 break; 508 509 case STATE_PARAM: 510 if (!(c==EOL || Parser.isWhitespace(c))) { 511 appendToBuf(c); 512 } 513 else if (c=='\n') { 514 paramName=bufToString(); 515 setBufLength(0); 516 state=STATE_PARAMWRAP; 517 } 518 else { 519 paramName=bufToString(); 520 setBufLength(0); 521 state=STATE_PARAMVALUE; 522 } 523 break; 524 525 case STATE_INLINEPARAM: 526 if (c=='}') { 527 // tag without value 528 paramName=bufToString(); 529 AbstractTagImpl newTag = addTag(tags, paramName, "", i<firstSentenceEnd, contextClass, contextMember, contextTag, true); 530 if (null != newTag) { 531 contextTag = newTag; 532 } 533 state=prevState; 534 setBufLength(0); 535 } 536 else if (!(c==EOL || Parser.isWhitespace(c))) { 537 appendToBuf(c); 538 } 539 else if (c=='\n') { 540 paramName=bufToString(); 541 setBufLength(0); 542 state=STATE_INLINEPARAMVALUE_BOL; 543 } 544 else { 545 paramName=bufToString(); 546 setBufLength(0); 547 state=STATE_INLINEPARAMVALUE; 548 } 549 break; 550 551 case STATE_PARAMVALUE: 552 if (c==EOL) { 553 paramValue=bufToString(); 554 AbstractTagImpl newTag = addTag(tags, paramName, paramValue, i<firstSentenceEnd, contextClass, contextMember, contextTag, false); 555 if (null != newTag) { 556 contextTag = newTag; 557 } 558 } 559 else if (c=='\n') { 560 appendToBuf(c); 561 state=STATE_PARAMWRAP; 562 } 563 else { 564 appendToBuf(c); 565 } 566 break; 567 568 case STATE_INLINEPARAMVALUE: 569 if (c=='\n') { 570 appendToBuf(c); 571 state=STATE_INLINEPARAMVALUE_BOL; 572 } 573 else if (c==EOL || c=='}') { 574 paramValue=bufToString(); 575 AbstractTagImpl newTag = addTag(tags, paramName, paramValue, i<firstSentenceEnd, contextClass, contextMember, contextTag, true); 576 if (null != newTag) { 577 contextTag = newTag; 578 } 579 state=prevState; 580 setBufLength(0); 581 } 582 else { 583 appendToBuf(c); 584 } 585 break; 586 587 case STATE_INLINEPARAMVALUE_BOL: 588 if (Parser.isWhitespace(c)) { 589 // ignore 590 } 591 else if (c=='*') { 592 // ignore, but go to STATE_TEXT 593 if (i<endIndex && peek!='*') { 594 state=STATE_IPV_WHITESPACE; 595 } 596 } 597 else if (c==EOL) { 598 if (bufLength()>0) { 599 AbstractTagImpl newTag = addTag(tags, "text", bufToString(), i<firstSentenceEnd, contextClass, contextMember, contextTag, false); 600 if (null != newTag) { 601 contextTag = newTag; 602 } 603 } 604 } 605 else { 606 state=STATE_INLINEPARAMVALUE; 607 appendToBuf(whitespaceBuf); 608 whitespaceBuf.setLength(0); 609 appendToBuf(c); 610 } 611 break; 612 613 case STATE_IPV_WHITESPACE: 614 if (c=='\n') { 615 whitespaceBuf.append(c); 616 state=STATE_INLINEPARAMVALUE_BOL; 617 } 618 else if (Parser.isWhitespace(c)) { 619 whitespaceBuf.append(c); 620 } 621 else if (c==EOL) { 622 if (bufLength()>0) { 623 AbstractTagImpl newTag = addTag(tags, "text", bufToString(), i<firstSentenceEnd, contextClass, contextMember, contextTag, false); 624 if (null != newTag) { 625 contextTag = newTag; 626 } 627 setBufLength(0); 628 } 629 } 630 else { 631 appendToBuf(whitespaceBuf); 632 whitespaceBuf.setLength(0); 633 appendToBuf(c); 634 state=STATE_INLINEPARAMVALUE; 635 } 636 break; 637 638 case STATE_TEXT: 639 if (i==firstSentenceEnd) { 640 AbstractTagImpl newTag = addTag(tags, "text", bufToString(), true, contextClass, contextMember, contextTag, false); 641 if (null != newTag) { 642 contextTag = newTag; 643 } 644 setBufLength(0); 645 } 646 647 if (c==EOL) { 648 paramValue=bufToString(); 649 AbstractTagImpl newTag = addTag(tags, "text", paramValue, i<firstSentenceEnd, contextClass, contextMember, contextTag, false); 650 if (null != newTag) { 651 contextTag = newTag; 652 } 653 } 654 else if (c=='\n') { 655 appendToBuf(c); 656 state=STATE_BEGOFLINE; 657 } 658 else if (c=='{' && peek=='@') { 659 paramValue=bufToString(); 660 AbstractTagImpl newTag = addTag(tags, "text", paramValue, i<firstSentenceEnd, contextClass, contextMember, contextTag, false); 661 if (null != newTag) { 662 contextTag = newTag; 663 } 664 ++i; 665 setBufLength(0); 666 state=STATE_INLINEPARAM; 667 } 668 else { 669 appendToBuf(c); 670 } 671 break; 672 673 default: 674 throw new Error("illegal state "+state); 675 } 676 } 677 678 679 if (null == contextMember && null != boilerplateComment && Main.getInstance().isCopyLicenseText()) { 680 addTag(tags, "@boilerplate", boilerplateComment, false, contextClass, null, null, false); 681 } 682 683 Map rc=new HashMap(); 684 685 for (Iterator it=tags.keySet().iterator(); it.hasNext(); ) { 686 String key=(String)it.next(); 687 Tag[] templateArr; 688 List list=(List)tags.get(key); 689 690 if ("see".equals(key)) 691 templateArr=new SeeTag[list.size()]; 692 else if ("param".equals(key)) 693 templateArr=new ParamTag[list.size()]; 694 else if ("serialField".equals(key)) 695 templateArr=new SerialFieldTag[list.size()]; 696 else if ("throws".equals(key) || "exception".equals(key)) 697 templateArr=new ThrowsTag[list.size()]; 698 else { 699 templateArr=new Tag[list.size()]; 700 } 701 702 rc.put(key, list.toArray(templateArr)); 703 } 704 705 return rc; 706 } 707 getContextClass()708 private ClassDocImpl getContextClass() { 709 if (isClass() || isInterface()) { 710 return (ClassDocImpl)this; 711 } 712 else if (isField() || isMethod() || isConstructor()) { 713 return (ClassDocImpl)((MemberDocImpl)this).containingClass(); 714 } 715 else { 716 return null; 717 } 718 } 719 getContextMember()720 private MemberDocImpl getContextMember() { 721 if (isField() || isMethod() || isConstructor()) { 722 return (MemberDocImpl)this; 723 } 724 else { 725 return null; 726 } 727 } 728 addTag(Map tags, String name, String value, boolean isFirstSentence, ClassDocImpl contextClass, MemberDocImpl contextMember, AbstractTagImpl contextTag, boolean isInline)729 protected static AbstractTagImpl addTag(Map tags, String name, 730 String value, boolean isFirstSentence, 731 ClassDocImpl contextClass, 732 MemberDocImpl contextMember, 733 AbstractTagImpl contextTag, 734 boolean isInline) { 735 736 AbstractTagImpl tag = null; 737 738 boolean haveValue = (0 != value.trim().length()); 739 740 String emptyWarning = "Empty @" + name + " tag."; 741 742 if (name.equals("param")) { 743 if (haveValue) { 744 tag=new ParamTagImpl(value, contextClass, contextMember); 745 } 746 else { 747 //printWarning(emptyWarning); 748 } 749 } 750 else if (name.equals("see")) { 751 if (haveValue) { 752 tag=new SeeTagImpl(value, contextClass); 753 } 754 else { 755 //printWarning(emptyWarning); 756 } 757 } 758 else if (name.equals("link") || name.equals("linkplain")) { 759 if (haveValue) { 760 tag=new LinkTagImpl("@" + name, value, contextClass); 761 isInline = true; 762 } 763 else { 764 //printWarning(emptyWarning); 765 } 766 } 767 else if (name.equals("value")) { 768 if (haveValue) { 769 tag=new ValueTagImpl(value, contextClass); 770 isInline = true; 771 } 772 else { 773 //printWarning(emptyWarning); 774 } 775 } 776 else if (name.equals("inheritDoc")) { 777 if (haveValue) { 778 //printWarning("@inheritDoc tags are not supposed to have any content."); 779 } 780 tag=new InheritDocTagImpl(contextClass, contextMember, contextTag); 781 isInline = true; 782 } 783 else if (name.equals("serialField")) { 784 if (haveValue) { 785 tag=new SerialFieldTagImpl(value, contextClass, contextMember); 786 } 787 else { 788 //printWarning(emptyWarning); 789 } 790 } 791 else if (name.equals("throws") || name.equals("exception")) { 792 if (haveValue) { 793 tag=new ThrowsTagImpl(value, contextClass, contextMember); 794 } 795 else { 796 //printWarning(emptyWarning); 797 } 798 name="throws"; 799 } 800 else if (name.equals("text")) { 801 tag=new TextTagImpl(value); 802 isInline = true; 803 } 804 else { 805 tag=new TagImpl("@"+name, value.trim(), contextClass, contextMember); 806 // FIXME: consider taglets 807 } 808 809 if (tag != null) { 810 if (isInline) { 811 ((List)tags.get("inline")).add(tag); 812 if (isFirstSentence) { 813 if (name.equals("text")) { 814 String txt = ((TextTagImpl)tag).getText(); 815 Tag newTag; 816 if (txt.startsWith("<p>")) { 817 newTag = new TextTagImpl(txt.substring(3)); 818 } 819 else if (txt.endsWith("</p>")) { 820 newTag = new TextTagImpl(txt.substring(0, txt.length() - 4)); 821 } 822 else { 823 newTag = tag; 824 } 825 ((List)tags.get("first")).add(newTag); 826 827 } 828 else { 829 ((List)tags.get("first")).add(tag); 830 } 831 } 832 } 833 else { 834 ((List)tags.get("all")).add(tag); 835 } 836 837 List l=((List)tags.get(name)); 838 if (l==null) { 839 l=new LinkedList(); 840 tags.put(name,l); 841 } 842 l.add(tag); 843 844 return isInline ? tag : contextTag; 845 } 846 else { 847 return null; 848 } 849 } 850 851 // Return all tags in this Doc item. tags()852 public Tag[] tags() { 853 Tag[] rc=(Tag[])tagMap.get("all"); 854 if (rc==null) rc=new Tag[0]; 855 return rc; 856 } 857 858 // Return tags of the specified kind in this Doc item. tags(java.lang.String tagname)859 public Tag[] tags(java.lang.String tagname) { 860 Tag[] rc=(Tag[])tagMap.get(tagname); 861 if (rc==null) rc=new Tag[0]; 862 return rc; 863 } 864 865 protected String rawDocumentation; 866 protected long rawDocOffset=-1; 867 868 protected Map tagMap = new HashMap(); 869 getTagMap()870 public Map getTagMap() { return tagMap; } 871 resolveTags()872 protected void resolveTags() { 873 874 Tag[] tags=tags(); 875 for (int i=0; i<tags.length; ++i) { 876 ((AbstractTagImpl)tags[i]).resolve(); 877 } 878 879 Tag[] inlineTags=inlineTags(); 880 for (int i=0; i<inlineTags.length; ++i) { 881 ((AbstractTagImpl)inlineTags[i]).resolve(); 882 } 883 } 884 885 private static Map classDocToFileMap = new HashMap(); 886 getFile(ClassDoc classDoc)887 private static File getFile(ClassDoc classDoc) { 888 File result = (File)classDocToFileMap.get(classDoc); 889 if (null == result) { 890 result = new File(((GjdocPackageDoc)classDoc.containingPackage()).packageDirectory(), 891 classDoc.name() + ".java"); 892 classDocToFileMap.put(classDoc, result); 893 } 894 return result; 895 } 896 getPosition(ClassDoc classDoc)897 public static SourcePosition getPosition(ClassDoc classDoc) 898 { 899 return new SourcePositionImpl(getFile(classDoc), 0, 0); 900 } 901 getPosition(ClassDoc classDoc, char[] source, int startIndex)902 public static SourcePosition getPosition(ClassDoc classDoc, char[] source, int startIndex) 903 { 904 int column = 0; 905 int line = 0; 906 for (int i=0; i<startIndex; ++i) { 907 if (10 == source[i]) { 908 ++ line; 909 column = 0; 910 } 911 else if (13 != source[i]) { 912 ++ column; 913 } 914 } 915 while (true) { 916 ClassDoc containingClassDoc = classDoc.containingClass(); 917 if (null != containingClassDoc) { 918 classDoc = containingClassDoc; 919 } 920 else { 921 break; 922 } 923 } 924 925 File file = getFile(classDoc); 926 927 return new SourcePositionImpl(file, line + 1, column + 1); 928 } 929 position()930 public SourcePosition position() 931 { 932 return this.position; 933 } 934 DocImpl(SourcePosition position)935 public DocImpl(SourcePosition position) 936 { 937 this.position = position; 938 } 939 setPosition(SourcePosition position)940 public void setPosition(SourcePosition position) 941 { 942 this.position = position; 943 } 944 checkForInheritedDoc(ClassDoc classDoc, MemberDocImpl memberDoc, AbstractTagImpl tag)945 private static TagContainer checkForInheritedDoc(ClassDoc classDoc, 946 MemberDocImpl memberDoc, 947 AbstractTagImpl tag) 948 { 949 DocImpl result; 950 951 if (!(classDoc instanceof ClassDocImpl)) { 952 result = null; 953 } 954 else if (null == memberDoc) { 955 result = (DocImpl)classDoc; 956 } 957 else if (memberDoc.isField()) { 958 result = (DocImpl)((ClassDocImpl)classDoc).getFieldDoc(memberDoc.name()); 959 } 960 else if (memberDoc.isMethod()) { 961 result = (DocImpl)((ClassDocImpl)classDoc).getMethodDoc(memberDoc.name(), 962 ((MethodDoc)memberDoc).signature()); 963 } 964 else if (memberDoc.isConstructor()) { 965 result = (DocImpl)((ClassDocImpl)classDoc).getConstructorDoc(((ConstructorDoc)memberDoc).signature()); 966 } 967 else { 968 //assert(false); 969 throw new RuntimeException("memberDoc is supposed to be field, method or constructor"); 970 } 971 972 if (null != result 973 && null != memberDoc 974 && null != tag) { 975 976 TagContainer tagDoc = null; 977 978 Tag[] tags = result.tags(); 979 for (int i=0; i<tags.length; ++i) { 980 if (tags[i].kind().equals(tag.kind())) { 981 if ("@param".equals(tag.kind())) { 982 if (((ParamTagImpl)tags[i]).parameterName().equals(((ParamTagImpl)tag).parameterName())) { 983 tagDoc = (TagContainer)tags[i]; 984 break; 985 } 986 } 987 else if ("@throws".equals(tag.kind())) { 988 if (((ThrowsTagImpl)tags[i]).exceptionName().equals(((ThrowsTagImpl)tag).exceptionName())) { 989 tagDoc = (TagContainer)tags[i]; 990 break; 991 } 992 } 993 else if ("@return".equals(tag.kind())) { 994 tagDoc = (TagContainer)tags[i]; 995 } 996 } 997 } 998 999 return tagDoc; 1000 } 1001 1002 if (null == result || result.isEmptyDoc()) { 1003 return null; 1004 } 1005 else { 1006 return result; 1007 } 1008 } 1009 findInheritedDoc(ClassDoc classDoc, MemberDocImpl memberDoc, AbstractTagImpl tag)1010 public static TagContainer findInheritedDoc(ClassDoc classDoc, 1011 MemberDocImpl memberDoc, 1012 AbstractTagImpl tag) 1013 { 1014 TagContainer result; 1015 1016 // (Taken from Javadoc Solaris Tool documentation 1.5, 1017 // section "Automatic Copying of Method Comments") 1018 1019 // Algorithm for Inheriting Method Comments - If a method does 1020 // not have a doc comment, or has an {@inheritDoc} tag, the 1021 // Javadoc tool searches for an applicable comment using the 1022 // following algorithm, which is designed to find the most 1023 // specific applicable doc comment, giving preference to 1024 // interfaces over superclasses: 1025 1026 // 1. Look in each directly implemented (or extended) interface 1027 // in the order they appear following the word implements (or 1028 // extends) in the method declaration. Use the first doc comment 1029 // found for this method. 1030 1031 ClassDoc[] interfaces = classDoc.interfaces(); 1032 if (null != interfaces) { 1033 for (int i=0; i<interfaces.length; ++i) { 1034 result = checkForInheritedDoc(interfaces[i], memberDoc, tag); 1035 if (null != result) { 1036 return result; 1037 } 1038 } 1039 } 1040 1041 // 2. If step 1 failed to find a doc comment, recursively apply 1042 // this entire algorithm to each directly implemented (or 1043 // extended) interface, in the same order they were examined 1044 // in step 1. 1045 1046 if (null != interfaces) { 1047 for (int i=0; i<interfaces.length; ++i) { 1048 result = findInheritedDoc(interfaces[i], memberDoc, tag); 1049 if (null != result) { 1050 return result; 1051 } 1052 } 1053 } 1054 1055 ClassDoc superclassDoc = classDoc.superclass(); 1056 1057 // 3. If step 2 failed to find a doc comment and this is a class 1058 // other than Object (not an interface): 1059 if (!classDoc.isInterface() 1060 && null != superclassDoc 1061 && !"java.lang.Object".equals(classDoc.qualifiedTypeName())) { 1062 1063 // 3a. If the superclass has a doc comment for this method, use it. 1064 1065 result = checkForInheritedDoc(superclassDoc, memberDoc, tag); 1066 if (null != result) { 1067 return result; 1068 } 1069 1070 // 3b. If step 3a failed to find a doc comment, recursively 1071 // apply this entire algorithm to the superclass. 1072 1073 return findInheritedDoc(superclassDoc, 1074 memberDoc, tag); 1075 } 1076 else { 1077 return null; 1078 } 1079 } 1080 isEmptyDoc()1081 public boolean isEmptyDoc() 1082 { 1083 return tagMap.isEmpty(); 1084 } 1085 setBoilerplateComment(String boilerplateComment)1086 void setBoilerplateComment(String boilerplateComment) 1087 { 1088 this.boilerplateComment = boilerplateComment; 1089 } 1090 } 1091