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