001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2022 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.javadoc; 021 022import java.util.Arrays; 023import java.util.BitSet; 024import java.util.Optional; 025import java.util.regex.Pattern; 026 027import com.puppycrawl.tools.checkstyle.StatelessCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailNode; 029import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 030import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 031import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 032import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 033 034/** 035 * <p> 036 * Checks that 037 * <a href="https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html#firstsentence"> 038 * Javadoc summary sentence</a> does not contain phrases that are not recommended to use. 039 * Summaries that contain only the {@code {@inheritDoc}} tag are skipped. 040 * Summaries that contain a non-empty {@code {@return}} are allowed. 041 * Check also violate Javadoc that does not contain first sentence, though with {@code {@return}} a 042 * period is not required as the Javadoc tool adds it. 043 * </p> 044 * <ul> 045 * <li> 046 * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations 047 * if the Javadoc being examined by this check violates the tight html rules defined at 048 * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">Tight-HTML Rules</a>. 049 * Type is {@code boolean}. 050 * Default value is {@code false}. 051 * </li> 052 * <li> 053 * Property {@code forbiddenSummaryFragments} - Specify the regexp for forbidden summary fragments. 054 * Type is {@code java.util.regex.Pattern}. 055 * Default value is {@code "^$"}. 056 * </li> 057 * <li> 058 * Property {@code period} - Specify the period symbol at the end of first javadoc sentence. 059 * Type is {@code java.lang.String}. 060 * Default value is {@code "."}. 061 * </li> 062 * </ul> 063 * <p> 064 * To configure the default check to validate that first sentence is not empty and first 065 * sentence is not missing: 066 * </p> 067 * <pre> 068 * <module name="SummaryJavadocCheck"/> 069 * </pre> 070 * <p> 071 * Example of {@code {@inheritDoc}} without summary. 072 * </p> 073 * <pre> 074 * public class Test extends Exception { 075 * //Valid 076 * /** 077 * * {@inheritDoc} 078 * */ 079 * public String ValidFunction(){ 080 * return ""; 081 * } 082 * //Violation 083 * /** 084 * * 085 * */ 086 * public String InvalidFunction(){ 087 * return ""; 088 * } 089 * } 090 * </pre> 091 * <p> 092 * Example of non permitted empty javadoc for Inline Summary Javadoc. 093 * </p> 094 * <pre> 095 * public class Test extends Exception { 096 * /** 097 * * {@summary } 098 * */ 099 * public String InvalidFunctionOne(){ // violation 100 * return ""; 101 * } 102 * 103 * /** 104 * * {@summary <p> <p/>} 105 * */ 106 * public String InvalidFunctionTwo(){ // violation 107 * return ""; 108 * } 109 * 110 * /** 111 * * {@summary <p>This is summary for validFunctionThree.<p/>} 112 * */ 113 * public void validFunctionThree(){} // ok 114 * } 115 * </pre> 116 * <p> 117 * To ensure that summary does not contain phrase like "This method returns", 118 * use following config: 119 * </p> 120 * <pre> 121 * <module name="SummaryJavadocCheck"> 122 * <property name="forbiddenSummaryFragments" 123 * value="^This method returns.*"/> 124 * </module> 125 * </pre> 126 * <p> 127 * To specify period symbol at the end of first javadoc sentence: 128 * </p> 129 * <pre> 130 * <module name="SummaryJavadocCheck"> 131 * <property name="period" value="。"/> 132 * </module> 133 * </pre> 134 * <p> 135 * Example of period property. 136 * </p> 137 * <pre> 138 * public class TestClass { 139 * /** 140 * * This is invalid java doc. 141 * */ 142 * void invalidJavaDocMethod() { 143 * } 144 * /** 145 * * This is valid java doc。 146 * */ 147 * void validJavaDocMethod() { 148 * } 149 * } 150 * </pre> 151 * <p> 152 * Example of period property for inline summary javadoc. 153 * </p> 154 * <pre> 155 * public class TestClass { 156 * /** 157 * * {@summary This is invalid java doc.} 158 * */ 159 * public void invalidJavaDocMethod() { // violation 160 * } 161 * /** 162 * * {@summary This is valid java doc。} 163 * */ 164 * public void validJavaDocMethod() { // ok 165 * } 166 * } 167 * </pre> 168 * <p> 169 * Example of inline summary javadoc with HTML tags. 170 * </p> 171 * <pre> 172 * public class Test { 173 * /** 174 * * {@summary First sentence is normally the summary. 175 * * Use of html tags: 176 * * <ul> 177 * * <li>Item one.</li> 178 * * <li>Item two.</li> 179 * * </ul>} 180 * */ 181 * public void validInlineJavadoc() { // ok 182 * } 183 * } 184 * </pre> 185 * <p> 186 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 187 * </p> 188 * <p> 189 * Violation Message Keys: 190 * </p> 191 * <ul> 192 * <li> 193 * {@code javadoc.missed.html.close} 194 * </li> 195 * <li> 196 * {@code javadoc.parse.rule.error} 197 * </li> 198 * <li> 199 * {@code javadoc.wrong.singleton.html.tag} 200 * </li> 201 * <li> 202 * {@code summary.first.sentence} 203 * </li> 204 * <li> 205 * {@code summary.javaDoc} 206 * </li> 207 * <li> 208 * {@code summary.javaDoc.missing} 209 * </li> 210 * <li> 211 * {@code summary.javaDoc.missing.period} 212 * </li> 213 * </ul> 214 * 215 * @since 6.0 216 */ 217@StatelessCheck 218public class SummaryJavadocCheck extends AbstractJavadocCheck { 219 220 /** 221 * A key is pointing to the warning message text in "messages.properties" 222 * file. 223 */ 224 public static final String MSG_SUMMARY_FIRST_SENTENCE = "summary.first.sentence"; 225 226 /** 227 * A key is pointing to the warning message text in "messages.properties" 228 * file. 229 */ 230 public static final String MSG_SUMMARY_JAVADOC = "summary.javaDoc"; 231 232 /** 233 * A key is pointing to the warning message text in "messages.properties" 234 * file. 235 */ 236 public static final String MSG_SUMMARY_JAVADOC_MISSING = "summary.javaDoc.missing"; 237 238 /** 239 * A key is pointing to the warning message text in "messages.properties" file. 240 */ 241 public static final String MSG_SUMMARY_MISSING_PERIOD = "summary.javaDoc.missing.period"; 242 243 /** 244 * This regexp is used to convert multiline javadoc to single-line without stars. 245 */ 246 private static final Pattern JAVADOC_MULTILINE_TO_SINGLELINE_PATTERN = 247 Pattern.compile("\n[ ]+(\\*)|^[ ]+(\\*)"); 248 249 /** 250 * This regexp is used to remove html tags, whitespace, and asterisks from a string. 251 */ 252 private static final Pattern HTML_ELEMENTS = 253 Pattern.compile("<[^>]*>"); 254 255 /** Default period literal. */ 256 private static final String DEFAULT_PERIOD = "."; 257 258 /** Summary tag text. */ 259 private static final String SUMMARY_TEXT = "@summary"; 260 261 /** Return tag text. */ 262 private static final String RETURN_TEXT = "@return"; 263 264 /** Set of allowed Tokens tags in summary java doc. */ 265 private static final BitSet ALLOWED_TYPES = TokenUtil.asBitSet( 266 JavadocTokenTypes.WS, 267 JavadocTokenTypes.DESCRIPTION, 268 JavadocTokenTypes.TEXT); 269 270 /** 271 * Specify the regexp for forbidden summary fragments. 272 */ 273 private Pattern forbiddenSummaryFragments = CommonUtil.createPattern("^$"); 274 275 /** 276 * Specify the period symbol at the end of first javadoc sentence. 277 */ 278 private String period = DEFAULT_PERIOD; 279 280 /** 281 * Setter to specify the regexp for forbidden summary fragments. 282 * 283 * @param pattern a pattern. 284 */ 285 public void setForbiddenSummaryFragments(Pattern pattern) { 286 forbiddenSummaryFragments = pattern; 287 } 288 289 /** 290 * Setter to specify the period symbol at the end of first javadoc sentence. 291 * 292 * @param period period's value. 293 */ 294 public void setPeriod(String period) { 295 this.period = period; 296 } 297 298 @Override 299 public int[] getDefaultJavadocTokens() { 300 return new int[] { 301 JavadocTokenTypes.JAVADOC, 302 }; 303 } 304 305 @Override 306 public int[] getRequiredJavadocTokens() { 307 return getAcceptableJavadocTokens(); 308 } 309 310 @Override 311 public void visitJavadocToken(DetailNode ast) { 312 final Optional<DetailNode> inlineTag = getInlineTagNode(ast); 313 final DetailNode inlineTagNode = inlineTag.orElse(null); 314 if (inlineTag.isPresent() 315 && isSummaryTag(inlineTagNode) 316 && isDefinedFirst(inlineTagNode)) { 317 validateSummaryTag(inlineTagNode); 318 } 319 else if (inlineTag.isPresent() && isInlineReturnTag(inlineTagNode)) { 320 validateInlineReturnTag(inlineTagNode); 321 } 322 else if (!startsWithInheritDoc(ast)) { 323 validateUntaggedSummary(ast); 324 } 325 } 326 327 /** 328 * Checks the javadoc text for {@code period} at end and forbidden fragments. 329 * 330 * @param ast the javadoc text node 331 */ 332 private void validateUntaggedSummary(DetailNode ast) { 333 final String summaryDoc = getSummarySentence(ast); 334 if (summaryDoc.isEmpty()) { 335 log(ast.getLineNumber(), MSG_SUMMARY_JAVADOC_MISSING); 336 } 337 else if (!period.isEmpty()) { 338 final String firstSentence = getFirstSentence(ast); 339 final int endOfSentence = firstSentence.lastIndexOf(period); 340 if (!summaryDoc.contains(period)) { 341 log(ast.getLineNumber(), MSG_SUMMARY_FIRST_SENTENCE); 342 } 343 if (endOfSentence != -1 344 && containsForbiddenFragment(firstSentence.substring(0, endOfSentence))) { 345 log(ast.getLineNumber(), MSG_SUMMARY_JAVADOC); 346 } 347 } 348 } 349 350 /** 351 * Gets the node for the inline tag if present. 352 * 353 * @param javadoc javadoc root node. 354 * @return the node for the inline tag if present. 355 */ 356 private static Optional<DetailNode> getInlineTagNode(DetailNode javadoc) { 357 return Arrays.stream(javadoc.getChildren()) 358 .filter(SummaryJavadocCheck::isInlineTagPresent) 359 .findFirst() 360 .map(SummaryJavadocCheck::getInlineTagNodeWithinHtmlElement); 361 } 362 363 /** 364 * Whether the {@code {@summary}} tag is defined first in the javadoc. 365 * 366 * @param inlineSummaryTag node of type {@link JavadocTokenTypes#JAVADOC_INLINE_TAG} 367 * @return {@code true} if the {@code {@summary}} tag is defined first in the javadoc 368 */ 369 private static boolean isDefinedFirst(DetailNode inlineSummaryTag) { 370 boolean isDefinedFirst = true; 371 DetailNode previousSibling = JavadocUtil.getPreviousSibling(inlineSummaryTag); 372 while (previousSibling != null && isDefinedFirst) { 373 switch (previousSibling.getType()) { 374 case JavadocTokenTypes.TEXT: 375 isDefinedFirst = previousSibling.getText().isBlank(); 376 break; 377 case JavadocTokenTypes.HTML_ELEMENT: 378 isDefinedFirst = !isTextPresentInsideHtmlTag(previousSibling); 379 break; 380 default: 381 break; 382 } 383 previousSibling = JavadocUtil.getPreviousSibling(previousSibling); 384 } 385 return isDefinedFirst; 386 } 387 388 /** 389 * Whether some text is present inside the HTML element or tag. 390 * 391 * @param node DetailNode of type {@link JavadocTokenTypes#HTML_TAG} 392 * or {@link JavadocTokenTypes#HTML_ELEMENT} 393 * @return {@code true} if some text is present inside the HTML element or tag 394 */ 395 public static boolean isTextPresentInsideHtmlTag(DetailNode node) { 396 DetailNode nestedChild = JavadocUtil.getFirstChild(node); 397 if (node.getType() == JavadocTokenTypes.HTML_ELEMENT) { 398 nestedChild = JavadocUtil.getFirstChild(nestedChild); 399 } 400 boolean isTextPresentInsideHtmlTag = false; 401 while (nestedChild != null && !isTextPresentInsideHtmlTag) { 402 switch (nestedChild.getType()) { 403 case JavadocTokenTypes.TEXT: 404 isTextPresentInsideHtmlTag = !nestedChild.getText().isBlank(); 405 break; 406 case JavadocTokenTypes.HTML_TAG: 407 case JavadocTokenTypes.HTML_ELEMENT: 408 isTextPresentInsideHtmlTag = isTextPresentInsideHtmlTag(nestedChild); 409 break; 410 default: 411 break; 412 } 413 nestedChild = JavadocUtil.getNextSibling(nestedChild); 414 } 415 return isTextPresentInsideHtmlTag; 416 } 417 418 /** 419 * Checks if the inline tag node is present. 420 * 421 * @param ast ast node to check. 422 * @return true, if the inline tag node is present. 423 */ 424 private static boolean isInlineTagPresent(DetailNode ast) { 425 return ast.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG 426 || ast.getType() == JavadocTokenTypes.HTML_ELEMENT 427 && getInlineTagNodeWithinHtmlElement(ast) != null; 428 } 429 430 /** 431 * Returns an inline javadoc tag node that is within a html tag. 432 * 433 * @param ast html tag node. 434 * @return inline summary javadoc tag node or null if no node is found. 435 */ 436 private static DetailNode getInlineTagNodeWithinHtmlElement(DetailNode ast) { 437 DetailNode node = ast; 438 DetailNode result = null; 439 // node can never be null as this method is called when there is a HTML_ELEMENT 440 if (node.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) { 441 result = node; 442 } 443 else if (node.getType() == JavadocTokenTypes.HTML_TAG) { 444 // HTML_TAG always has more than 2 children. 445 node = node.getChildren()[1]; 446 result = getInlineTagNodeWithinHtmlElement(node); 447 } 448 else if (node.getType() == JavadocTokenTypes.HTML_ELEMENT 449 // Condition for SINGLETON html element which cannot contain summary node 450 && node.getChildren()[0].getChildren().length > 1) { 451 // Html elements have one tested tag before actual content inside it 452 node = node.getChildren()[0].getChildren()[1]; 453 result = getInlineTagNodeWithinHtmlElement(node); 454 } 455 return result; 456 } 457 458 /** 459 * Checks if the javadoc inline tag is {@code {@summary}} tag. 460 * 461 * @param javadocInlineTag node of type {@link JavadocTokenTypes#JAVADOC_INLINE_TAG} 462 * @return {@code true} if inline tag is summary tag. 463 */ 464 private static boolean isSummaryTag(DetailNode javadocInlineTag) { 465 return isInlineTagWithName(javadocInlineTag, SUMMARY_TEXT); 466 } 467 468 /** 469 * Checks if the first tag inside ast is {@code {@return}} tag. 470 * 471 * @param javadocInlineTag node of type {@link JavadocTokenTypes#JAVADOC_INLINE_TAG} 472 * @return {@code true} if first tag is return tag. 473 */ 474 private static boolean isInlineReturnTag(DetailNode javadocInlineTag) { 475 return isInlineTagWithName(javadocInlineTag, RETURN_TEXT); 476 } 477 478 /** 479 * Checks if the first tag inside ast is a tag with the given name. 480 * 481 * @param javadocInlineTag node of type {@link JavadocTokenTypes#JAVADOC_INLINE_TAG} 482 * @param name name of inline tag. 483 * 484 * @return {@code true} if first tag is a tag with the given name. 485 */ 486 private static boolean isInlineTagWithName(DetailNode javadocInlineTag, String name) { 487 final DetailNode[] child = javadocInlineTag.getChildren(); 488 489 // Checking size of ast is not required, since ast contains 490 // children of Inline Tag, as at least 2 children will be present which are 491 // RCURLY and LCURLY. 492 return child[1].getType() == JavadocTokenTypes.CUSTOM_NAME 493 && name.equals(child[1].getText()); 494 } 495 496 /** 497 * Checks the inline summary (if present) for {@code period} at end and forbidden fragments. 498 * 499 * @param inlineSummaryTag node of type {@link JavadocTokenTypes#JAVADOC_INLINE_TAG} 500 */ 501 private void validateSummaryTag(DetailNode inlineSummaryTag) { 502 final String inlineSummary = getContentOfInlineCustomTag(inlineSummaryTag); 503 final String summaryVisible = getVisibleContent(inlineSummary); 504 if (summaryVisible.isEmpty()) { 505 log(inlineSummaryTag.getLineNumber(), MSG_SUMMARY_JAVADOC_MISSING); 506 } 507 else if (!period.isEmpty()) { 508 if (isPeriodNotAtEnd(summaryVisible, period)) { 509 log(inlineSummaryTag.getLineNumber(), MSG_SUMMARY_MISSING_PERIOD); 510 } 511 else if (containsForbiddenFragment(inlineSummary)) { 512 log(inlineSummaryTag.getLineNumber(), MSG_SUMMARY_JAVADOC); 513 } 514 } 515 } 516 517 /** 518 * Checks the inline return for forbidden fragments. 519 * 520 * @param inlineReturnTag node of type {@link JavadocTokenTypes#JAVADOC_INLINE_TAG} 521 */ 522 private void validateInlineReturnTag(DetailNode inlineReturnTag) { 523 final String inlineReturn = getContentOfInlineCustomTag(inlineReturnTag); 524 final String returnVisible = getVisibleContent(inlineReturn); 525 if (returnVisible.isEmpty()) { 526 log(inlineReturnTag.getLineNumber(), MSG_SUMMARY_JAVADOC_MISSING); 527 } 528 else if (containsForbiddenFragment(inlineReturn)) { 529 log(inlineReturnTag.getLineNumber(), MSG_SUMMARY_JAVADOC); 530 } 531 } 532 533 /** 534 * Gets the content of inline custom tag. 535 * 536 * @param inlineTag inline tag node. 537 * @return String consisting of the content of inline custom tag. 538 */ 539 public static String getContentOfInlineCustomTag(DetailNode inlineTag) { 540 final DetailNode[] childrenOfInlineTag = inlineTag.getChildren(); 541 final StringBuilder customTagContent = new StringBuilder(256); 542 final int indexOfContentOfSummaryTag = 3; 543 if (childrenOfInlineTag.length != indexOfContentOfSummaryTag) { 544 DetailNode currentNode = childrenOfInlineTag[indexOfContentOfSummaryTag]; 545 while (currentNode.getType() != JavadocTokenTypes.JAVADOC_INLINE_TAG_END) { 546 extractInlineTagContent(currentNode, customTagContent); 547 currentNode = JavadocUtil.getNextSibling(currentNode); 548 } 549 } 550 return customTagContent.toString(); 551 } 552 553 /** 554 * Extracts the content of inline custom tag recursively. 555 * 556 * @param node DetailNode 557 * @param customTagContent content of custom tag 558 */ 559 private static void extractInlineTagContent(DetailNode node, 560 StringBuilder customTagContent) { 561 final DetailNode[] children = node.getChildren(); 562 if (children.length == 0) { 563 customTagContent.append(node.getText()); 564 } 565 else { 566 for (DetailNode child : children) { 567 if (child.getType() != JavadocTokenTypes.LEADING_ASTERISK) { 568 extractInlineTagContent(child, customTagContent); 569 } 570 } 571 } 572 } 573 574 /** 575 * Gets the string that is visible to user in javadoc. 576 * 577 * @param summary entire content of summary javadoc. 578 * @return string that is visible to user in javadoc. 579 */ 580 private static String getVisibleContent(String summary) { 581 final String visibleSummary = HTML_ELEMENTS.matcher(summary).replaceAll(""); 582 return visibleSummary.trim(); 583 } 584 585 /** 586 * Checks if the string does not end with period. 587 * 588 * @param sentence string to check for period at end. 589 * @param period string to check within sentence. 590 * @return {@code true} if sentence does not end with period. 591 */ 592 private static boolean isPeriodNotAtEnd(String sentence, String period) { 593 final String summarySentence = sentence.trim(); 594 return summarySentence.lastIndexOf(period) != summarySentence.length() - 1; 595 } 596 597 /** 598 * Tests if first sentence contains forbidden summary fragment. 599 * 600 * @param firstSentence string with first sentence. 601 * @return {@code true} if first sentence contains forbidden summary fragment. 602 */ 603 private boolean containsForbiddenFragment(String firstSentence) { 604 final String javadocText = JAVADOC_MULTILINE_TO_SINGLELINE_PATTERN 605 .matcher(firstSentence).replaceAll(" ").trim(); 606 return forbiddenSummaryFragments.matcher(trimExcessWhitespaces(javadocText)).find(); 607 } 608 609 /** 610 * Trims the given {@code text} of duplicate whitespaces. 611 * 612 * @param text the text to transform. 613 * @return the finalized form of the text. 614 */ 615 private static String trimExcessWhitespaces(String text) { 616 final StringBuilder result = new StringBuilder(256); 617 boolean previousWhitespace = true; 618 619 for (char letter : text.toCharArray()) { 620 final char print; 621 if (Character.isWhitespace(letter)) { 622 if (previousWhitespace) { 623 continue; 624 } 625 626 previousWhitespace = true; 627 print = ' '; 628 } 629 else { 630 previousWhitespace = false; 631 print = letter; 632 } 633 634 result.append(print); 635 } 636 637 return result.toString(); 638 } 639 640 /** 641 * Checks if the node starts with an {@inheritDoc}. 642 * 643 * @param root the root node to examine. 644 * @return {@code true} if the javadoc starts with an {@inheritDoc}. 645 */ 646 private static boolean startsWithInheritDoc(DetailNode root) { 647 boolean found = false; 648 final DetailNode[] children = root.getChildren(); 649 650 for (int i = 0; !found; i++) { 651 final DetailNode child = children[i]; 652 if (child.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG 653 && child.getChildren()[1].getType() == JavadocTokenTypes.INHERIT_DOC_LITERAL) { 654 found = true; 655 } 656 else if (child.getType() != JavadocTokenTypes.LEADING_ASTERISK 657 && !CommonUtil.isBlank(child.getText())) { 658 break; 659 } 660 } 661 662 return found; 663 } 664 665 /** 666 * Finds and returns summary sentence. 667 * 668 * @param ast javadoc root node. 669 * @return violation string. 670 */ 671 private static String getSummarySentence(DetailNode ast) { 672 final StringBuilder result = new StringBuilder(256); 673 for (DetailNode child : ast.getChildren()) { 674 if (child.getType() == JavadocTokenTypes.JAVADOC_TAG) { 675 break; 676 } 677 if (child.getType() != JavadocTokenTypes.EOF 678 && ALLOWED_TYPES.get(child.getType())) { 679 result.append(child.getText()); 680 } 681 else { 682 final String summary = result.toString(); 683 if (child.getType() == JavadocTokenTypes.HTML_ELEMENT 684 && CommonUtil.isBlank(summary)) { 685 result.append(getStringInsideTag(summary, 686 child.getChildren()[0].getChildren()[0])); 687 } 688 } 689 } 690 return result.toString().trim(); 691 } 692 693 /** 694 * Get concatenated string within text of html tags. 695 * 696 * @param result javadoc string 697 * @param detailNode javadoc tag node 698 * @return java doc tag content appended in result 699 */ 700 private static String getStringInsideTag(String result, DetailNode detailNode) { 701 final StringBuilder contents = new StringBuilder(result); 702 DetailNode tempNode = detailNode; 703 while (tempNode != null) { 704 if (tempNode.getType() == JavadocTokenTypes.TEXT) { 705 contents.append(tempNode.getText()); 706 } 707 tempNode = JavadocUtil.getNextSibling(tempNode); 708 } 709 return contents.toString(); 710 } 711 712 /** 713 * Finds and returns first sentence. 714 * 715 * @param ast Javadoc root node. 716 * @return first sentence. 717 */ 718 private static String getFirstSentence(DetailNode ast) { 719 final StringBuilder result = new StringBuilder(256); 720 final String periodSuffix = DEFAULT_PERIOD + ' '; 721 for (DetailNode child : ast.getChildren()) { 722 final String text; 723 if (child.getChildren().length == 0) { 724 text = child.getText(); 725 } 726 else { 727 text = getFirstSentence(child); 728 } 729 730 if (text.contains(periodSuffix)) { 731 result.append(text, 0, text.indexOf(periodSuffix) + 1); 732 break; 733 } 734 735 result.append(text); 736 } 737 return result.toString(); 738 } 739 740}