001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2021 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.meta; 021 022import java.util.ArrayDeque; 023import java.util.Arrays; 024import java.util.Collections; 025import java.util.Deque; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.LinkedHashSet; 029import java.util.Locale; 030import java.util.Map; 031import java.util.Optional; 032import java.util.Set; 033import java.util.regex.Matcher; 034import java.util.regex.Pattern; 035import java.util.stream.Collectors; 036 037import javax.xml.parsers.ParserConfigurationException; 038import javax.xml.transform.TransformerException; 039 040import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 041import com.puppycrawl.tools.checkstyle.api.DetailAST; 042import com.puppycrawl.tools.checkstyle.api.DetailNode; 043import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 044import com.puppycrawl.tools.checkstyle.api.TokenTypes; 045import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck; 046import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 047 048/** 049 * Class for scraping module metadata from the corresponding class' class-level javadoc. 050 */ 051@FileStatefulCheck 052public class JavadocMetadataScraper extends AbstractJavadocCheck { 053 054 /** Module details store used for testing. */ 055 private static final Map<String, ModuleDetails> MODULE_DETAILS_STORE = new HashMap<>(); 056 057 /** Regular expression for property location in class-level javadocs. */ 058 private static final Pattern PROPERTY_TAG = Pattern.compile("\\s*Property\\s*"); 059 060 /** Regular expression for property type location in class-level javadocs. */ 061 private static final Pattern TYPE_TAG = Pattern.compile("^ Type is\\s.*"); 062 063 /** Regular expression for property validation type location in class-level javadocs. */ 064 private static final Pattern VALIDATION_TYPE_TAG = 065 Pattern.compile("\\s.*Validation type is\\s.*"); 066 067 /** Regular expression for property default value location in class-level javadocs. */ 068 private static final Pattern DEFAULT_VALUE_TAG = Pattern.compile("^ Default value is:*.*"); 069 070 /** Regular expression for check example location in class-level javadocs. */ 071 private static final Pattern EXAMPLES_TAG = 072 Pattern.compile("\\s*To configure the (default )?check.*"); 073 074 /** Regular expression for module parent location in class-level javadocs. */ 075 private static final Pattern PARENT_TAG = Pattern.compile("\\s*Parent is\\s*"); 076 077 /** Regular expression for module violation messages location in class-level javadocs. */ 078 private static final Pattern VIOLATION_MESSAGES_TAG = 079 Pattern.compile("\\s*Violation Message Keys:\\s*"); 080 081 /** Regular expression for detecting ANTLR tokens(for e.g. CLASS_DEF). */ 082 private static final Pattern TOKEN_TEXT_PATTERN = Pattern.compile("([A-Z_]{2,})+"); 083 084 /** Regular expression for removal of @code{-} present at the beginning of texts. */ 085 private static final Pattern DESC_CLEAN = Pattern.compile("-\\s"); 086 087 /** Regular expression for file separator corresponding to the host OS. */ 088 private static final Pattern FILE_SEPARATOR_PATTERN = 089 Pattern.compile(Pattern.quote(System.getProperty("file.separator"))); 090 091 /** Regular expression for quotes. */ 092 private static final Pattern QUOTE_PATTERN = Pattern.compile("\""); 093 094 /** Java file extension. */ 095 private static final String JAVA_FILE_EXTENSION = ".java"; 096 097 /** 098 * This set contains faulty property default value which should not be written to the XML 099 * metadata files. 100 */ 101 private static final Set<String> PROPERTIES_TO_NOT_WRITE = Collections.unmodifiableSet( 102 new HashSet<>(Arrays.asList( 103 "null", 104 "the charset property of the parent <a href=https://checkstyle.org/" 105 + "config.html#Checker>Checker</a> module" 106 ))); 107 108 /** 109 * Format for exception message for missing type for check property. 110 */ 111 private static final String PROP_TYPE_MISSING = "Type for property '%s' is missing"; 112 113 /** 114 * Format for exception message for missing default value for check property. 115 */ 116 private static final String PROP_DEFAULT_VALUE_MISSING = 117 "Default value for property '%s' is missing"; 118 119 /** ModuleDetails instance for each module AST traversal. */ 120 private ModuleDetails moduleDetails; 121 122 /** 123 * Boolean variable which lets us know whether violation message section is being scraped 124 * currently. 125 */ 126 private boolean scrapingViolationMessageList; 127 128 /** 129 * Boolean variable which lets us know whether we should scan and scrape the current javadoc 130 * or not. Since we need only class level javadoc, it becomes true at its root and false after 131 * encountering {@code JavadocTokenTypes.SINCE_LITERAL}. 132 */ 133 private boolean toScan; 134 135 /** DetailNode pointing to the root node of the class level javadoc of the class. */ 136 private DetailNode rootNode; 137 138 /** 139 * Child number of the property section node, where parent is the class level javadoc root 140 * node. 141 */ 142 private int propertySectionStartIdx; 143 144 /** 145 * Child number of the example section node, where parent is the class level javadoc root 146 * node. 147 */ 148 private int exampleSectionStartIdx; 149 150 /** 151 * Child number of the parent section node, where parent is the class level javadoc root 152 * node. 153 */ 154 private int parentSectionStartIdx; 155 156 /** 157 * Control whether to write XML output or not. 158 */ 159 private boolean writeXmlOutput = true; 160 161 /** 162 * Setter to control whether to write XML output or not. 163 * 164 * @param writeXmlOutput whether to write XML output or not. 165 */ 166 public final void setWriteXmlOutput(boolean writeXmlOutput) { 167 this.writeXmlOutput = writeXmlOutput; 168 } 169 170 @Override 171 public int[] getDefaultJavadocTokens() { 172 return new int[] { 173 JavadocTokenTypes.JAVADOC, 174 JavadocTokenTypes.PARAGRAPH, 175 JavadocTokenTypes.LI, 176 JavadocTokenTypes.SINCE_LITERAL, 177 }; 178 } 179 180 @Override 181 public int[] getRequiredJavadocTokens() { 182 return getAcceptableJavadocTokens(); 183 } 184 185 @Override 186 public void beginJavadocTree(DetailNode rootAst) { 187 if (isTopLevelClassJavadoc()) { 188 moduleDetails = new ModuleDetails(); 189 toScan = false; 190 scrapingViolationMessageList = false; 191 propertySectionStartIdx = -1; 192 exampleSectionStartIdx = -1; 193 parentSectionStartIdx = -1; 194 195 final String filePath = getFileContents().getFileName(); 196 String moduleName = getModuleSimpleName(); 197 final String checkModuleExtension = "Check"; 198 if (moduleName.endsWith(checkModuleExtension)) { 199 moduleName = moduleName 200 .substring(0, moduleName.length() - checkModuleExtension.length()); 201 } 202 moduleDetails.setName(moduleName); 203 moduleDetails.setFullQualifiedName(getPackageName(filePath)); 204 moduleDetails.setModuleType(getModuleType()); 205 } 206 } 207 208 @Override 209 public void visitJavadocToken(DetailNode ast) { 210 if (toScan) { 211 scrapeContent(ast); 212 } 213 214 if (ast.getType() == JavadocTokenTypes.JAVADOC) { 215 final DetailAST parent = getParent(getBlockCommentAst()); 216 if (parent.getType() == TokenTypes.CLASS_DEF) { 217 rootNode = ast; 218 toScan = true; 219 } 220 } 221 else if (ast.getType() == JavadocTokenTypes.SINCE_LITERAL) { 222 toScan = false; 223 } 224 } 225 226 @Override 227 public void finishJavadocTree(DetailNode rootAst) { 228 moduleDetails.setDescription(getDescriptionText()); 229 if (isTopLevelClassJavadoc()) { 230 if (writeXmlOutput) { 231 try { 232 XmlMetaWriter.write(moduleDetails); 233 } 234 catch (TransformerException | ParserConfigurationException ex) { 235 throw new IllegalStateException("Failed to write metadata into XML file for " 236 + "module: " + getModuleSimpleName(), ex); 237 } 238 } 239 else { 240 MODULE_DETAILS_STORE.put(moduleDetails.getFullQualifiedName(), moduleDetails); 241 } 242 } 243 } 244 245 /** 246 * Method containing the core logic of scraping. This keeps track and decides which phase of 247 * scraping we are in, and accordingly call other subroutines. 248 * 249 * @param ast javadoc ast 250 */ 251 private void scrapeContent(DetailNode ast) { 252 if (ast.getType() == JavadocTokenTypes.PARAGRAPH) { 253 if (isParentText(ast)) { 254 parentSectionStartIdx = getParentIndexOf(ast); 255 moduleDetails.setParent(getParentText(ast)); 256 } 257 else if (isViolationMessagesText(ast)) { 258 scrapingViolationMessageList = true; 259 } 260 else if (exampleSectionStartIdx == -1 261 && isExamplesText(ast)) { 262 exampleSectionStartIdx = getParentIndexOf(ast); 263 } 264 } 265 else if (ast.getType() == JavadocTokenTypes.LI) { 266 if (isPropertyList(ast)) { 267 if (propertySectionStartIdx == -1) { 268 propertySectionStartIdx = getParentIndexOf(ast); 269 } 270 moduleDetails.addToProperties(createProperties(ast)); 271 } 272 else if (scrapingViolationMessageList) { 273 moduleDetails.addToViolationMessages(getViolationMessages(ast)); 274 } 275 } 276 } 277 278 /** 279 * Create the modulePropertyDetails content. 280 * 281 * @param nodeLi list item javadoc node 282 * @return modulePropertyDetail object for the corresponding property 283 */ 284 private static ModulePropertyDetails createProperties(DetailNode nodeLi) { 285 final ModulePropertyDetails modulePropertyDetails = new ModulePropertyDetails(); 286 287 final Optional<DetailNode> propertyNameNode = getFirstChildOfType(nodeLi, 288 JavadocTokenTypes.JAVADOC_INLINE_TAG, 0); 289 if (propertyNameNode.isPresent()) { 290 final DetailNode propertyNameTag = propertyNameNode.get(); 291 final String propertyName = getTextFromTag(propertyNameTag); 292 293 final DetailNode propertyType = getFirstChildOfMatchingText(nodeLi, TYPE_TAG) 294 .orElseThrow(() -> { 295 return new MetadataGenerationException(String.format( 296 Locale.ROOT, PROP_TYPE_MISSING, propertyName) 297 ); 298 }); 299 final String propertyDesc = DESC_CLEAN.matcher( 300 constructSubTreeText(nodeLi, propertyNameTag.getIndex() + 1, 301 propertyType.getIndex() - 1)) 302 .replaceAll(Matcher.quoteReplacement("")); 303 304 modulePropertyDetails.setDescription(propertyDesc.trim()); 305 modulePropertyDetails.setName(propertyName); 306 modulePropertyDetails.setType(getTagTextFromProperty(nodeLi, propertyType)); 307 308 final Optional<DetailNode> validationTypeNodeOpt = getFirstChildOfMatchingText(nodeLi, 309 VALIDATION_TYPE_TAG); 310 if (validationTypeNodeOpt.isPresent()) { 311 final DetailNode validationTypeNode = validationTypeNodeOpt.get(); 312 modulePropertyDetails.setValidationType(getTagTextFromProperty(nodeLi, 313 validationTypeNode)); 314 } 315 316 final String defaultValue = getFirstChildOfMatchingText(nodeLi, DEFAULT_VALUE_TAG) 317 .map(defaultValueNode -> getPropertyDefaultText(nodeLi, defaultValueNode)) 318 .orElseThrow(() -> { 319 return new MetadataGenerationException(String.format( 320 Locale.ROOT, PROP_DEFAULT_VALUE_MISSING, propertyName) 321 ); 322 }); 323 if (!PROPERTIES_TO_NOT_WRITE.contains(defaultValue)) { 324 modulePropertyDetails.setDefaultValue(defaultValue); 325 } 326 } 327 return modulePropertyDetails; 328 } 329 330 /** 331 * Get tag text from property data. 332 * 333 * @param nodeLi javadoc li item node 334 * @param propertyMeta property javadoc node 335 * @return property metadata text 336 */ 337 private static String getTagTextFromProperty(DetailNode nodeLi, DetailNode propertyMeta) { 338 final Optional<DetailNode> tagNodeOpt = getFirstChildOfType(nodeLi, 339 JavadocTokenTypes.JAVADOC_INLINE_TAG, propertyMeta.getIndex() + 1); 340 DetailNode tagNode = null; 341 if (tagNodeOpt.isPresent()) { 342 tagNode = tagNodeOpt.get(); 343 } 344 return getTextFromTag(tagNode); 345 } 346 347 /** 348 * Clean up the default token text by removing hyperlinks, and only keeping token type text. 349 * 350 * @param initialText unclean text 351 * @return clean text 352 */ 353 private static String cleanDefaultTokensText(String initialText) { 354 final Set<String> tokens = new LinkedHashSet<>(); 355 final Matcher matcher = TOKEN_TEXT_PATTERN.matcher(initialText); 356 while (matcher.find()) { 357 tokens.add(matcher.group(0)); 358 } 359 return String.join(",", tokens); 360 } 361 362 /** 363 * Performs a DFS of the subtree with a node as the root and constructs the text of that 364 * tree, ignoring JavadocToken texts. 365 * 366 * @param node root node of subtree 367 * @param childLeftLimit the left index of root children from where to scan 368 * @param childRightLimit the right index of root children till where to scan 369 * @return constructed text of subtree 370 */ 371 private static String constructSubTreeText(DetailNode node, int childLeftLimit, 372 int childRightLimit) { 373 final StringBuilder result = new StringBuilder(1024); 374 DetailNode detailNode = node; 375 376 final Deque<DetailNode> stack = new ArrayDeque<>(); 377 stack.addFirst(detailNode); 378 final Set<DetailNode> visited = new HashSet<>(); 379 while (!stack.isEmpty()) { 380 detailNode = stack.getFirst(); 381 stack.removeFirst(); 382 383 if (!visited.contains(detailNode)) { 384 final String childText = detailNode.getText(); 385 if (detailNode.getType() != JavadocTokenTypes.LEADING_ASTERISK 386 && !TOKEN_TEXT_PATTERN.matcher(childText).matches()) { 387 result.insert(0, detailNode.getText()); 388 } 389 visited.add(detailNode); 390 } 391 392 for (DetailNode child : detailNode.getChildren()) { 393 if (child.getParent().equals(node) 394 && (child.getIndex() < childLeftLimit 395 || child.getIndex() > childRightLimit)) { 396 continue; 397 } 398 if (!visited.contains(child)) { 399 stack.addFirst(child); 400 } 401 } 402 } 403 return result.toString().trim(); 404 } 405 406 /** 407 * Create the description text with starting index as 0 and ending index would be the first 408 * valid non zero index amongst in the order of {@code propertySectionStartIdx}, 409 * {@code exampleSectionStartIdx} and {@code parentSectionStartIdx}. 410 * 411 * @return description text 412 */ 413 private String getDescriptionText() { 414 final int descriptionEndIdx; 415 if (propertySectionStartIdx > -1) { 416 descriptionEndIdx = propertySectionStartIdx; 417 } 418 else if (exampleSectionStartIdx > -1) { 419 descriptionEndIdx = exampleSectionStartIdx; 420 } 421 else { 422 descriptionEndIdx = parentSectionStartIdx; 423 } 424 return constructSubTreeText(rootNode, 0, descriptionEndIdx - 1); 425 } 426 427 /** 428 * Create property default text, which is either normal property value or list of tokens. 429 * 430 * @param nodeLi list item javadoc node 431 * @param defaultValueNode default value node 432 * @return default property text 433 */ 434 private static String getPropertyDefaultText(DetailNode nodeLi, DetailNode defaultValueNode) { 435 final Optional<DetailNode> propertyDefaultValueTag = getFirstChildOfType(nodeLi, 436 JavadocTokenTypes.JAVADOC_INLINE_TAG, defaultValueNode.getIndex() + 1); 437 final String result; 438 if (propertyDefaultValueTag.isPresent()) { 439 result = getTextFromTag(propertyDefaultValueTag.get()); 440 } 441 else { 442 final String tokenText = constructSubTreeText(nodeLi, 443 defaultValueNode.getIndex(), nodeLi.getChildren().length); 444 result = cleanDefaultTokensText(tokenText); 445 } 446 return result; 447 } 448 449 /** 450 * Get the violation message text for a specific key from the list item. 451 * 452 * @param nodeLi list item javadoc node 453 * @return violation message key text 454 */ 455 private static String getViolationMessages(DetailNode nodeLi) { 456 final Optional<DetailNode> resultNode = getFirstChildOfType(nodeLi, 457 JavadocTokenTypes.JAVADOC_INLINE_TAG, 0); 458 return resultNode.map(JavadocMetadataScraper::getTextFromTag).orElse(""); 459 } 460 461 /** 462 * Get text from {@code JavadocTokenTypes.JAVADOC_INLINE_TAG}. 463 * 464 * @param nodeTag target javadoc tag 465 * @return text contained by the tag 466 */ 467 private static String getTextFromTag(DetailNode nodeTag) { 468 return Optional.ofNullable(nodeTag).map(JavadocMetadataScraper::getText).orElse(""); 469 } 470 471 /** 472 * Returns the first child node which matches the provided {@code TokenType} and has the 473 * children index after the offset value. 474 * 475 * @param node parent node 476 * @param tokenType token type to match 477 * @param offset children array index offset 478 * @return the first child satisfying the conditions 479 */ 480 private static Optional<DetailNode> getFirstChildOfType(DetailNode node, int tokenType, 481 int offset) { 482 return Arrays.stream(node.getChildren()) 483 .filter(child -> child.getIndex() >= offset && child.getType() == tokenType) 484 .findFirst(); 485 } 486 487 /** 488 * Get joined text from all text children nodes. 489 * 490 * @param parentNode parent node 491 * @return the joined text of node 492 */ 493 private static String getText(DetailNode parentNode) { 494 return Arrays.stream(parentNode.getChildren()) 495 .filter(child -> child.getType() == JavadocTokenTypes.TEXT) 496 .map(node -> QUOTE_PATTERN.matcher(node.getText().trim()).replaceAll("")) 497 .collect(Collectors.joining(" ")); 498 } 499 500 /** 501 * Get first child of parent node matching the provided pattern. 502 * 503 * @param node parent node 504 * @param pattern pattern to match against 505 * @return the first child node matching the condition 506 */ 507 private static Optional<DetailNode> getFirstChildOfMatchingText(DetailNode node, 508 Pattern pattern) { 509 return Arrays.stream(node.getChildren()) 510 .filter(child -> pattern.matcher(child.getText()).matches()) 511 .findFirst(); 512 } 513 514 /** 515 * Returns parent node, removing modifier/annotation nodes. 516 * 517 * @param commentBlock child node. 518 * @return parent node. 519 */ 520 private static DetailAST getParent(DetailAST commentBlock) { 521 final DetailAST parentNode = commentBlock.getParent(); 522 DetailAST result = parentNode; 523 if (result.getType() == TokenTypes.ANNOTATION) { 524 result = parentNode.getParent().getParent(); 525 } 526 else if (result.getType() == TokenTypes.MODIFIERS) { 527 result = parentNode.getParent(); 528 } 529 return result; 530 } 531 532 /** 533 * Traverse parents until we reach the root node (@code{JavadocTokenTypes.JAVADOC}) 534 * child and return its index. 535 * 536 * @param node subtree child node 537 * @return root node child index 538 */ 539 private static int getParentIndexOf(DetailNode node) { 540 DetailNode currNode = node; 541 while (currNode.getParent().getIndex() != -1) { 542 currNode = currNode.getParent(); 543 } 544 return currNode.getIndex(); 545 } 546 547 /** 548 * Get module parent text from paragraph javadoc node. 549 * 550 * @param nodeParagraph paragraph javadoc node 551 * @return parent text 552 */ 553 private static String getParentText(DetailNode nodeParagraph) { 554 return getFirstChildOfType(nodeParagraph, JavadocTokenTypes.JAVADOC_INLINE_TAG, 0) 555 .map(JavadocMetadataScraper::getTextFromTag) 556 .orElse(null); 557 } 558 559 /** 560 * Get module type(check/filter/filefilter) based on file name. 561 * 562 * @return module type 563 */ 564 private ModuleType getModuleType() { 565 final String simpleModuleName = getModuleSimpleName(); 566 final ModuleType result; 567 if (simpleModuleName.endsWith("FileFilter")) { 568 result = ModuleType.FILEFILTER; 569 } 570 else if (simpleModuleName.endsWith("Filter")) { 571 result = ModuleType.FILTER; 572 } 573 else { 574 result = ModuleType.CHECK; 575 } 576 return result; 577 } 578 579 /** 580 * Extract simple file name from the whole file path name. 581 * 582 * @return simple module name 583 */ 584 private String getModuleSimpleName() { 585 final String fullFileName = getFileContents().getFileName(); 586 final String[] pathTokens = FILE_SEPARATOR_PATTERN.split(fullFileName); 587 final String fileName = pathTokens[pathTokens.length - 1]; 588 return fileName.substring(0, fileName.length() - JAVA_FILE_EXTENSION.length()); 589 } 590 591 /** 592 * Retrieve package name of module from the absolute file path. 593 * 594 * @param filePath absolute file path 595 * @return package name 596 */ 597 private static String getPackageName(String filePath) { 598 final Deque<String> result = new ArrayDeque<>(); 599 final String[] filePathTokens = FILE_SEPARATOR_PATTERN.split(filePath); 600 for (int i = filePathTokens.length - 1; i >= 0; i--) { 601 if ("java".equals(filePathTokens[i]) || "resources".equals(filePathTokens[i])) { 602 break; 603 } 604 result.addFirst(filePathTokens[i]); 605 } 606 final String fileName = result.removeLast(); 607 result.addLast(fileName.substring(0, fileName.length() - JAVA_FILE_EXTENSION.length())); 608 return String.join(".", result); 609 } 610 611 /** 612 * Getter method for {@code moduleDetailsStore}. 613 * 614 * @return map containing module details of supplied checks. 615 */ 616 public static Map<String, ModuleDetails> getModuleDetailsStore() { 617 return Collections.unmodifiableMap(MODULE_DETAILS_STORE); 618 } 619 620 /** Reset the module detail store of any previous information. */ 621 public static void resetModuleDetailsStore() { 622 MODULE_DETAILS_STORE.clear(); 623 } 624 625 /** 626 * Check if the current javadoc block comment AST corresponds to the top-level class as we 627 * only want to scrape top-level class javadoc. 628 * 629 * @return true if the current AST corresponds to top level class 630 */ 631 private boolean isTopLevelClassJavadoc() { 632 final DetailAST parent = getParent(getBlockCommentAst()); 633 final Optional<DetailAST> className = TokenUtil 634 .findFirstTokenByPredicate(parent, child -> { 635 return parent.getType() == TokenTypes.CLASS_DEF 636 && child.getType() == TokenTypes.IDENT; 637 }); 638 return className.isPresent() 639 && getModuleSimpleName().equals(className.get().getText()); 640 } 641 642 /** 643 * Checks whether the paragraph node corresponds to the example section. 644 * 645 * @param ast javadoc paragraph node 646 * @return true if the section matches the example section marker 647 */ 648 private static boolean isExamplesText(DetailNode ast) { 649 return isChildNodeTextMatches(ast, EXAMPLES_TAG); 650 } 651 652 /** 653 * Checks whether the list item node is part of a property list. 654 * 655 * @param nodeLi {@code JavadocTokenType.LI} node 656 * @return true if the node is part of a property list 657 */ 658 private static boolean isPropertyList(DetailNode nodeLi) { 659 return isChildNodeTextMatches(nodeLi, PROPERTY_TAG); 660 } 661 662 /** 663 * Checks whether the {@code JavadocTokenType.PARAGRAPH} node is referring to the violation 664 * message keys javadoc segment. 665 * 666 * @param nodeParagraph paragraph javadoc node 667 * @return true if paragraph node contains the violation message keys text 668 */ 669 private static boolean isViolationMessagesText(DetailNode nodeParagraph) { 670 return isChildNodeTextMatches(nodeParagraph, VIOLATION_MESSAGES_TAG); 671 } 672 673 /** 674 * Checks whether the {@code JavadocTokenType.PARAGRAPH} node is referring to the parent 675 * javadoc segment. 676 * 677 * @param nodeParagraph paragraph javadoc node 678 * @return true if paragraph node contains the parent text 679 */ 680 private static boolean isParentText(DetailNode nodeParagraph) { 681 return isChildNodeTextMatches(nodeParagraph, PARENT_TAG); 682 } 683 684 /** 685 * Checks whether the first child {@code JavadocTokenType.TEXT} node matches given pattern. 686 * 687 * @param ast parent javadoc node 688 * @param pattern pattern to match 689 * @return true if one of child text nodes matches pattern 690 */ 691 private static boolean isChildNodeTextMatches(DetailNode ast, Pattern pattern) { 692 return getFirstChildOfType(ast, JavadocTokenTypes.TEXT, 0) 693 .map(DetailNode::getText) 694 .map(pattern::matcher) 695 .map(Matcher::matches) 696 .orElse(false); 697 } 698}