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