001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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.ArrayList; 023import java.util.List; 024import java.util.regex.Pattern; 025 026import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailNode; 028import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 030 031/** 032 * <div> 033 * Checks that Javadoc lines efficiently utilize the available horizontal space. 034 * </div> 035 * 036 * <p> 037 * Every line of a Javadoc comment is compared with {@code lineLimit}, the preferred line 038 * length. A line is reported when it is longer than the limit, or when it stops early although 039 * the next line would still have fit on it. Blank lines are never reported. 040 * The examples on this page use the default limit of 80 characters, and show the Javadoc 041 * of a method, indented by four spaces. 042 * </p> 043 * 044 * <p> 045 * <b>Lines that stop too early.</b> A line is reported when the whole next line would 046 * still fit on it. The fix is to join the two lines, so the comment gets one line shorter. 047 * When only a part of the next line would fit, the line is not reported, because moving 048 * words up would not make the comment shorter. Lines are never joined with a line that is 049 * blank, or that starts with a block tag, an inline tag, a URL or an HTML tag, so the line 050 * before such a line is not reported. 051 * </p> 052 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 053 * // violation, the second line would fit on the first line 054 * /** 055 * * Not part of the API. This is returned when a type 056 * * is not known. 057 * */ 058 * 059 * // ok, only a part of the second line would fit on the first line 060 * /** 061 * * Not part of the API. This is returned when a type 062 * * not known to this wrapper is returned. 063 * */ 064 * </code></pre></div> 065 * 066 * <p> 067 * <b>Lines that could take more words.</b> When {@code validateOnlyJoinableLines} is set 068 * to {@code false}, words are expected to fill a line up to the limit, so a line is also 069 * reported when only the first word of the next line would still fit on it. The fix is to 070 * move words up until the next word does not fit. 071 * </p> 072 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 073 * // violation, "not" would fit on the first line 074 * /** 075 * * Not part of the API. This is returned when a type 076 * * not known to this wrapper is returned. 077 * */ 078 * 079 * // ok 080 * /** 081 * * Not part of the API. This is returned when a type not known to this 082 * * wrapper is returned. 083 * */ 084 * </code></pre></div> 085 * 086 * <p> 087 * <b>Lines that end with punctuation.</b> A line that matches {@code ignoreTooShortPattern} 088 * is not reported as stopping too early, and no words are moved up to it. By default this is 089 * every line that ends with a period, a colon or a comma, so a sentence, or a part of it, 090 * can end before the limit. Trailing whitespace of the line is ignored when the pattern is 091 * applied. The line after such a line is checked on its own. Such lines can still be 092 * reported as too long. 093 * </p> 094 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 095 * // ok, the first line ends with a period 096 * /** 097 * * Not part of the API. 098 * * This is returned when a type not known to this wrapper is returned. 099 * */ 100 * 101 * // violation, the third line would fit on the second line 102 * /** 103 * * Not part of the API. 104 * * This is returned when a type not 105 * * known to this wrapper is returned. 106 * */ 107 * </code></pre></div> 108 * 109 * <p> 110 * <b>Lines that are too long.</b> A line is reported when it is longer than the limit. 111 * The fix is to wrap it at a word. 112 * </p> 113 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 114 * // violation, longer than 80 characters 115 * /** 116 * * Creates a client for the service and loads its settings from the given file path. 117 * */ 118 * 119 * // ok 120 * /** 121 * * Creates a client for the service and loads its settings from the given 122 * * file path. 123 * */ 124 * </code></pre></div> 125 * 126 * <p> 127 * <b>Block tags.</b> The value of a block tag, such as {@code @param}, {@code @return} 128 * or {@code @throws}, is checked like any other text. A line that has only the name of 129 * the block tag is not reported, so the value can start on the next line. A line that 130 * starts with a block tag is never joined to the line before it. 131 * </p> 132 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 133 * // violation, longer than 80 characters 134 * /** 135 * * @return Date the company was closed or dissolved, see {@link #getStatus()}. 136 * */ 137 * 138 * // ok, the value starts on the next line 139 * /** 140 * * @return 141 * * Date the company was closed or dissolved, see {@link #getStatus()}. 142 * */ 143 * 144 * // ok, wrapped before the inline tag 145 * /** 146 * * @return Date the company was closed or dissolved, see 147 * * {@link #getStatus()}. 148 * */ 149 * </code></pre></div> 150 * 151 * <p> 152 * <b>Inline tags.</b> An inline tag, such as a link to a class, cannot be wrapped. A line 153 * that starts with an inline tag and has nothing else on it is not reported, even when it is 154 * longer than the limit. When other words follow on the same line, the line is reported, 155 * because those words can move to the next line. A block tag never gets this exception for 156 * its value. 157 * </p> 158 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 159 * // ok, a tag alone on a line may be longer than 80 characters 160 * /** 161 * * Refer to the method 162 * * {@link com.example.registry.status.CompanyStatusRegistry#findStatus(String)} 163 * */ 164 * 165 * // violation, other words follow the tag 166 * /** 167 * * {@link com.example.registry.status.CompanyStatusRegistry#findStatus(String)} finds it. 168 * */ 169 * </code></pre></div> 170 * 171 * <p> 172 * <b>URLs and links.</b> A URL cannot be wrapped, so a line that matches 173 * {@code ignorePattern} is not reported as too long. By default this is every line that has 174 * a URL or the {@code href} attribute of a link. A block tag line whose value starts with a 175 * link, such as {@code @see <a href="...">}, is therefore not reported, whether the link ends 176 * on that line or on a later one. Such lines can still be reported as stopping too early. 177 * </p> 178 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 179 * // ok, lines that have a URL or a link may be longer than 80 characters 180 * /** 181 * * See https://example.com/documentation/company-registry/api/v2/status-endpoints/details 182 * * 183 * * @see <a href="https://example.com/documentation/company-registry/api/v2/status"> 184 * * Status endpoints</a> 185 * */ 186 * </code></pre></div> 187 * 188 * <p> 189 * <b>HTML.</b> A line that starts with an HTML tag, such as {@code <p>} or {@code </li>}, is 190 * not reported as too short, and it is not joined to the line before it, so tags can be 191 * placed on their own lines. Such a line, and the text inside an HTML element, must still 192 * not be longer than the limit. 193 * </p> 194 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 195 * // ok, a line that starts with an HTML tag is never too short 196 * /** 197 * * <p>Companies House allows you to query 198 * * information on registered companies in Britain.</p> 199 * */ 200 * 201 * // violation, text inside an HTML element is checked 202 * /** 203 * * <ul> 204 * * <li>Static configuration reads files from the classpath or a given path.</li> 205 * * </ul> 206 * */ 207 * 208 * // ok 209 * /** 210 * * <ul> 211 * * <li>Static configuration reads files from the classpath or a given 212 * * path.</li> 213 * * </ul> 214 * */ 215 * </code></pre></div> 216 * 217 * <p> 218 * <b>Pre and code elements.</b> The content of {@code <pre>} and {@code <code>} elements, 219 * and HTML comments, is ignored, so code samples can keep their own formatting. 220 * </p> 221 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 222 * // ok, the content of a pre element is ignored 223 * /** 224 * * Example: 225 * * <pre> 226 * * new CompanyRegistry().findStatus("00000006").orElseThrow(NotRegisteredException::new); 227 * * </pre> 228 * */ 229 * </code></pre></div> 230 * 231 * @since 14.4.0 232 */ 233@FileStatefulCheck 234public class JavadocUtilizingTrailingSpaceCheck extends AbstractJavadocCheck { 235 236 /** Message key for too short Javadoc line. */ 237 public static final String MSG_TOO_SHORT = "javadoc.utilizing.trailing.space.too.short"; 238 239 /** Message key for too long Javadoc line. */ 240 public static final String MSG_TOO_LONG = "javadoc.utilizing.trailing.space.too.long"; 241 242 /** Pattern that recognizes URLs. */ 243 private static final Pattern URL_PATTERN = Pattern.compile("https?://|ftp://"); 244 245 /** Pattern for the indentation and the leading asterisk of a Javadoc line. */ 246 private static final Pattern LINE_PREFIX = Pattern.compile("^\\s*\\*?\\s*"); 247 248 /** Default maximum line length. */ 249 private static final int DEFAULT_LINE_LIMIT = 80; 250 251 /** Collects logical Javadoc lines for the current Javadoc comment. */ 252 private final List<JavadocLine> lines = new ArrayList<>(); 253 254 /** Configurable line length limit. */ 255 private int lineLimit = DEFAULT_LINE_LIMIT; 256 257 /** Pattern for lines that are not reported as too long. */ 258 private Pattern ignorePattern = 259 Pattern.compile("href\\s*=\\s*\"[^\"]*\"|http://|https://|ftp://"); 260 261 /** Pattern for lines that are not reported as too short. */ 262 private Pattern ignoreTooShortPattern = Pattern.compile("[.:,]$"); 263 264 /** Control whether a line is reported as too short only when the whole next line fits on it. */ 265 private boolean validateOnlyJoinableLines = true; 266 267 /** Current line being built. */ 268 private JavadocLine currentLine; 269 270 /** The node whose subtree is currently being skipped, or null. */ 271 private DetailNode skipRoot; 272 273 /** 274 * Creates a new {@code JavadocUtilizingTrailingSpaceCheck} instance. 275 */ 276 public JavadocUtilizingTrailingSpaceCheck() { 277 // no code by default 278 } 279 280 @Override 281 public int[] getDefaultJavadocTokens() { 282 return new int[] { 283 JavadocCommentsTokenTypes.NEWLINE, 284 JavadocCommentsTokenTypes.TEXT, 285 JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG, 286 JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG, 287 JavadocCommentsTokenTypes.HTML_ELEMENT, 288 JavadocCommentsTokenTypes.HTML_COMMENT, 289 JavadocCommentsTokenTypes.HTML_TAG_START, 290 JavadocCommentsTokenTypes.HTML_TAG_END, 291 JavadocCommentsTokenTypes.VOID_ELEMENT, 292 }; 293 } 294 295 @Override 296 public int[] getRequiredJavadocTokens() { 297 return getDefaultJavadocTokens(); 298 } 299 300 /** 301 * Setter to specify the line length limit. 302 * 303 * @param limit the maximum length to target for Javadoc lines 304 * @since 14.4.0 305 */ 306 public void setLineLimit(int limit) { 307 lineLimit = limit; 308 } 309 310 /** 311 * Setter to specify pattern for lines that are not reported as too long. 312 * 313 * @param pattern a pattern. 314 * @since 14.4.0 315 */ 316 public void setIgnorePattern(Pattern pattern) { 317 ignorePattern = pattern; 318 } 319 320 /** 321 * Setter to specify pattern for lines that are not reported as too short. 322 * 323 * @param pattern a pattern. 324 * @since 14.4.0 325 */ 326 public void setIgnoreTooShortPattern(Pattern pattern) { 327 ignoreTooShortPattern = pattern; 328 } 329 330 /** 331 * Setter to control whether a line is reported as too short only when the whole next 332 * line fits on it. 333 * 334 * @param validateOnlyJoinableLines true if a line is reported as too short only when 335 * the whole next line fits on it, false if the first word of the next line is enough. 336 * @since 14.4.0 337 */ 338 public void setValidateOnlyJoinableLines(boolean validateOnlyJoinableLines) { 339 this.validateOnlyJoinableLines = validateOnlyJoinableLines; 340 } 341 342 @Override 343 public void beginJavadocTree(DetailNode rootAst) { 344 lines.clear(); 345 currentLine = null; 346 } 347 348 @Override 349 public void finishJavadocTree(DetailNode rootAst) { 350 commitCurrentLine(); 351 validateLines(); 352 } 353 354 @Override 355 public void visitJavadocToken(DetailNode ast) { 356 if (skipRoot == null) { 357 switch (ast.getType()) { 358 case JavadocCommentsTokenTypes.NEWLINE -> handleNewline(ast); 359 case JavadocCommentsTokenTypes.TEXT -> handleText(ast); 360 case JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG -> handleBlockTag(ast); 361 case JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG -> handleInlineTag(ast); 362 case JavadocCommentsTokenTypes.HTML_ELEMENT -> handleHtmlElement(ast); 363 case JavadocCommentsTokenTypes.HTML_COMMENT -> skipNode(ast); 364 default -> handleHtmlTag(ast); 365 } 366 } 367 } 368 369 @Override 370 public void leaveJavadocToken(DetailNode ast) { 371 if (ast == skipRoot) { 372 skipRoot = null; 373 } 374 } 375 376 /** 377 * Validates all collected lines for violations. 378 */ 379 private void validateLines() { 380 for (int index = 0; index < lines.size(); index++) { 381 final JavadocLine line = lines.get(index); 382 383 if (!line.hasContent) { 384 continue; 385 } 386 387 if (line.length > lineLimit && !line.isOnlyInlineTag() 388 && !ignorePattern.matcher(getLine(line.lineNumber - 1)).find()) { 389 log(line.firstNode, MSG_TOO_LONG, lineLimit, line.length); 390 } 391 392 if (isTooShort(index)) { 393 log(line.firstNode, MSG_TOO_SHORT, lineLimit, line.length); 394 } 395 } 396 } 397 398 /** 399 * Decides whether the line at the given index is too short 400 * (i.e. we should have pulled content from the next line). 401 * 402 * @param index the line index to check 403 * @return true if the line is too short 404 */ 405 private boolean isTooShort(int index) { 406 return !lines.get(index).startsWithHtml 407 && canPullFromNextLine(index) 408 && (index == 0 409 || !lines.get(index - 1).hasContent 410 || !canPullFromNextLine(index - 1)); 411 } 412 413 /** 414 * Handles a newline token. 415 * 416 * @param node the newline node 417 */ 418 private void handleNewline(DetailNode node) { 419 initCurrentLine(node.getLineNumber()); 420 } 421 422 /** 423 * Handles a Javadoc block tag node. A line that starts a block tag cannot 424 * be merged into the previous line, but its value is still validated. 425 * 426 * @param node the block tag node 427 */ 428 private void handleBlockTag(DetailNode node) { 429 initCurrentLine(node.getLineNumber()); 430 currentLine.startsWithBlockTag = true; 431 currentLine.firstNode = node; 432 } 433 434 /** 435 * Handles a TEXT node. 436 * 437 * @param node the text node 438 */ 439 private void handleText(DetailNode node) { 440 final String text = node.getText(); 441 442 initCurrentLine(node.getLineNumber()); 443 currentLine.updateLength(node.getColumnNumber(), text.length()); 444 445 final String trimmed = text.trim(); 446 if (!trimmed.isEmpty()) { 447 if (currentLine.hasContent) { 448 currentLine.markAdditionalContent(text); 449 } 450 else { 451 currentLine.initContent(trimmed, URL_PATTERN, node); 452 } 453 } 454 } 455 456 /** 457 * Handles a Javadoc inline tag node. The tag is measured as a whole and its content is skipped. 458 * 459 * @param node the inline tag node 460 */ 461 private void handleInlineTag(DetailNode node) { 462 initCurrentLine(node.getLineNumber()); 463 updateLengthToEnd(node); 464 465 if (currentLine.hasContent) { 466 currentLine.hasAdditionalContent = true; 467 } 468 else { 469 currentLine.hasContent = true; 470 if (!currentLine.startsWithBlockTag) { 471 currentLine.startsWithUnbreakable = true; 472 currentLine.startsWithInlineTag = true; 473 currentLine.firstNode = node; 474 } 475 } 476 skipRoot = node; 477 } 478 479 /** 480 * Handles an HTML element. The content of {@code pre} and {@code code} elements is skipped. 481 * 482 * @param element the HTML element node 483 */ 484 private void handleHtmlElement(DetailNode element) { 485 if (JavadocUtil.isTag(element, "pre") || JavadocUtil.isTag(element, "code")) { 486 skipNode(element); 487 } 488 } 489 490 /** 491 * Handles an HTML start tag, end tag or void element. A line that starts 492 * with such a tag is never reported as too short, but it can still be too long. 493 * 494 * @param node the HTML tag node 495 */ 496 private void handleHtmlTag(DetailNode node) { 497 initCurrentLine(node.getLineNumber()); 498 if (!currentLine.hasContent) { 499 currentLine.hasContent = true; 500 if (!currentLine.startsWithBlockTag) { 501 currentLine.startsWithHtml = true; 502 currentLine.firstNode = node; 503 } 504 } 505 updateLengthToEnd(node); 506 skipRoot = node; 507 } 508 509 /** 510 * Skips the subtree of the given node. If the node spans several lines, the 511 * line where it starts is marked, because the lines after it are not adjacent 512 * to the lines that were skipped. 513 * 514 * @param node the node to skip 515 */ 516 private void skipNode(DetailNode node) { 517 initCurrentLine(node.getLineNumber()); 518 519 final DetailNode end = lastLeaf(node); 520 if (end.getLineNumber() == node.getLineNumber()) { 521 currentLine.updateLength(end.getColumnNumber(), end.getText().length()); 522 } 523 else { 524 currentLine.skippedLinesFollow = true; 525 } 526 skipRoot = node; 527 } 528 529 /** 530 * Extends the current line to the end of the given node, if the node ends on 531 * the line where it starts. 532 * 533 * @param node the node to measure 534 */ 535 private void updateLengthToEnd(DetailNode node) { 536 final DetailNode end = lastLeaf(node); 537 if (end.getLineNumber() == node.getLineNumber()) { 538 currentLine.updateLength(end.getColumnNumber(), end.getText().length()); 539 } 540 } 541 542 /** 543 * Finds the last leaf of the subtree of the given node. 544 * 545 * @param node the root of the subtree 546 * @return the last leaf, which is the node itself if it has no children 547 */ 548 private static DetailNode lastLeaf(DetailNode node) { 549 DetailNode leaf = node; 550 DetailNode child = leaf.getFirstChild(); 551 while (child != null) { 552 while (child.getNextSibling() != null) { 553 child = child.getNextSibling(); 554 } 555 leaf = child; 556 child = leaf.getFirstChild(); 557 } 558 return leaf; 559 } 560 561 /** 562 * Checks if content can be pulled from the next line. 563 * 564 * @param currentIndex current line index 565 * @return true if the content to pull from the next content line fits within lineLimit 566 */ 567 private boolean canPullFromNextLine(int currentIndex) { 568 boolean pullNext = false; 569 570 final JavadocLine line = lines.get(currentIndex); 571 572 if (!line.skippedLinesFollow && currentIndex + 1 < lines.size() 573 && !ignoreTooShortPattern.matcher( 574 getLine(line.lineNumber - 1).stripTrailing()).find()) { 575 576 final JavadocLine nextLine = lines.get(currentIndex + 1); 577 578 if (nextLine.hasContent && !nextLine.startsWithUnbreakable 579 && !nextLine.startsWithBlockTag && !nextLine.startsWithHtml) { 580 final int potentialLength = line.length + 1 + getPullLength(nextLine); 581 pullNext = potentialLength <= lineLimit; 582 } 583 } 584 585 return pullNext; 586 } 587 588 /** 589 * Returns the length of the content that has to fit on the line before the given 590 * line, for that line to be too short. 591 * 592 * @param nextLine the line to pull content from 593 * @return the length of the whole content of the line if only joinable lines are 594 * validated, the length of its first word otherwise 595 */ 596 private int getPullLength(JavadocLine nextLine) { 597 final int pullLength; 598 if (validateOnlyJoinableLines) { 599 final String sourceLine = getLine(nextLine.lineNumber - 1); 600 final int contentStart = sourceLine.length() 601 - LINE_PREFIX.matcher(sourceLine).replaceFirst("").length(); 602 pullLength = nextLine.length - contentStart; 603 } 604 else { 605 pullLength = nextLine.firstWordLength(); 606 } 607 return pullLength; 608 } 609 610 /** 611 * Initializes the current line if needed. 612 * 613 * @param lineNumber the source line number 614 */ 615 private void initCurrentLine(int lineNumber) { 616 if (currentLine == null || currentLine.lineNumber != lineNumber) { 617 commitCurrentLine(); 618 currentLine = new JavadocLine(lineNumber); 619 } 620 } 621 622 /** 623 * Commits the current line to the lines list and clears it. 624 */ 625 private void commitCurrentLine() { 626 if (currentLine != null) { 627 lines.add(currentLine); 628 } 629 } 630 631 /** 632 * Represents a logical line in a Javadoc comment. 633 */ 634 private static final class JavadocLine { 635 636 /** The source line number. */ 637 private final int lineNumber; 638 639 /** The first node with content on this line, the location of violations. */ 640 private DetailNode firstNode; 641 642 /** The length of the line (end column). */ 643 private int length; 644 645 /** Whether the line has meaningful content. */ 646 private boolean hasContent; 647 648 /** Whether the line starts with an unbreakable element (inline tag or URL). */ 649 private boolean startsWithUnbreakable; 650 651 /** Whether the line starts with an inline tag. */ 652 private boolean startsWithInlineTag; 653 654 /** Whether the line has content after its leading inline tag. */ 655 private boolean hasAdditionalContent; 656 657 /** Whether the line starts with a Javadoc block tag. */ 658 private boolean startsWithBlockTag; 659 660 /** Whether the line starts with an HTML tag. */ 661 private boolean startsWithHtml; 662 663 /** Whether skipped lines follow this line, so the next line is not adjacent. */ 664 private boolean skippedLinesFollow; 665 666 /** The first word of the line, used for pull calculations. */ 667 private String firstWord; 668 669 /** 670 * Creates a JavadocLine. 671 * 672 * @param lineNumber the source line number 673 */ 674 private JavadocLine(int lineNumber) { 675 this.lineNumber = lineNumber; 676 } 677 678 /** 679 * Updates the line length based on column position and content length. 680 * 681 * @param startColumn the starting column (0-indexed) 682 * @param contentLength the length of the content 683 */ 684 private void updateLength(int startColumn, int contentLength) { 685 length = startColumn + contentLength; 686 } 687 688 /** 689 * Initializes content state for the first non-empty text on this line. 690 * 691 * @param trimmedText the trimmed text content 692 * @param urlPattern pattern used to detect URLs 693 * @param textNode the node of the text content 694 */ 695 private void initContent(String trimmedText, Pattern urlPattern, 696 DetailNode textNode) { 697 hasContent = true; 698 firstWord = trimmedText.split(" ", 2)[0]; 699 if (!startsWithBlockTag) { 700 firstNode = textNode; 701 startsWithUnbreakable = urlPattern.matcher(trimmedText).lookingAt(); 702 } 703 } 704 705 /** 706 * Marks the line as having additional content when the given text, 707 * which follows the first content of the line, contains a separate word. 708 * Text attached to the preceding element, such as trailing punctuation, 709 * is not additional content. 710 * 711 * @param text the text that follows the first content of the line 712 */ 713 private void markAdditionalContent(String text) { 714 if (text.stripTrailing().chars().anyMatch(Character::isWhitespace)) { 715 hasAdditionalContent = true; 716 } 717 } 718 719 /** 720 * Checks whether the only content of this line is a single inline tag, 721 * which may exceed the limit. 722 * 723 * @return true if the line contains only an inline tag 724 */ 725 private boolean isOnlyInlineTag() { 726 return startsWithInlineTag && !hasAdditionalContent; 727 } 728 729 /** 730 * Returns the length of the first word on this line. 731 * 732 * @return the first word length 733 */ 734 private int firstWordLength() { 735 return firstWord.length(); 736 } 737 738 } 739 740}