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