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