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
437 return switch (codeChild.getType()) {
438 case JavadocTokenTypes.JAVADOC_INLINE_TAG_END -> "</code>";
439 case JavadocTokenTypes.WS -> "";
440 case JavadocTokenTypes.CODE_LITERAL -> codeChild.getText().replace("@", "") + ">";
441 case JavadocTokenTypes.JAVADOC_INLINE_TAG_START -> "<";
442 default -> codeChild.getText();
443 };
444 }
445
446 /**
447 * Create the description text with starting index as 0 and ending index would be the first
448 * valid non-zero index amongst in the order of {@code propertySectionStartIdx},
449 * {@code exampleSectionStartIdx} and {@code parentSectionStartIdx}.
450 *
451 * @return description text
452 */
453 private String getDescriptionText() {
454 final int descriptionEndIdx;
455 if (propertySectionStartIdx > -1) {
456 descriptionEndIdx = propertySectionStartIdx;
457 }
458 else if (exampleSectionStartIdx > -1) {
459 descriptionEndIdx = exampleSectionStartIdx;
460 }
461 else {
462 descriptionEndIdx = parentSectionStartIdx;
463 }
464 return constructSubTreeText(rootNode, 0, descriptionEndIdx - 1);
465 }
466
467 /**
468 * Create property default text, which is either normal property value or list of tokens.
469 *
470 * @param nodeLi list item javadoc node
471 * @param defaultValueNode default value node
472 * @return default property text
473 */
474 private static String getPropertyDefaultText(DetailNode nodeLi, DetailNode defaultValueNode) {
475 final Optional<DetailNode> propertyDefaultValueTag = getFirstChildOfType(nodeLi,
476 JavadocTokenTypes.JAVADOC_INLINE_TAG, defaultValueNode.getIndex() + 1);
477 final String result;
478 if (propertyDefaultValueTag.isPresent()) {
479 result = getTextFromTag(propertyDefaultValueTag.orElseThrow());
480 }
481 else {
482 final String tokenText = constructSubTreeText(nodeLi,
483 defaultValueNode.getIndex(), nodeLi.getChildren().length);
484 result = cleanDefaultTokensText(tokenText);
485 }
486 return result;
487 }
488
489 /**
490 * Get the violation message text for a specific key from the list item.
491 *
492 * @param nodeLi list item javadoc node
493 * @return violation message key text
494 */
495 private static String getViolationMessages(DetailNode nodeLi) {
496 final Optional<DetailNode> resultNode = getFirstChildOfType(nodeLi,
497 JavadocTokenTypes.JAVADOC_INLINE_TAG, 0);
498 return resultNode.map(JavadocMetadataScraper::getTextFromTag).orElse("");
499 }
500
501 /**
502 * Get text from {@code JavadocTokenTypes.JAVADOC_INLINE_TAG}.
503 *
504 * @param nodeTag target javadoc tag
505 * @return text contained by the tag
506 */
507 private static String getTextFromTag(DetailNode nodeTag) {
508 return Optional.ofNullable(nodeTag).map(JavadocMetadataScraper::getText).orElse("");
509 }
510
511 /**
512 * Returns the first child node which matches the provided {@code TokenType} and has the
513 * children index after the offset value.
514 *
515 * @param node parent node
516 * @param tokenType token type to match
517 * @param offset children array index offset
518 * @return the first child satisfying the conditions
519 */
520 private static Optional<DetailNode> getFirstChildOfType(DetailNode node, int tokenType,
521 int offset) {
522 return Arrays.stream(node.getChildren())
523 .filter(child -> child.getIndex() >= offset && child.getType() == tokenType)
524 .findFirst();
525 }
526
527 /**
528 * Get joined text from all text children nodes.
529 *
530 * @param parentNode parent node
531 * @return the joined text of node
532 */
533 private static String getText(DetailNode parentNode) {
534 return Arrays.stream(parentNode.getChildren())
535 .filter(child -> child.getType() == JavadocTokenTypes.TEXT)
536 .map(node -> QUOTE_PATTERN.matcher(node.getText().trim()).replaceAll(""))
537 .collect(Collectors.joining(" "));
538 }
539
540 /**
541 * Get first child of parent node matching the provided pattern.
542 *
543 * @param node parent node
544 * @param pattern pattern to match against
545 * @return the first child node matching the condition
546 */
547 private static Optional<DetailNode> getFirstChildOfMatchingText(DetailNode node,
548 Pattern pattern) {
549 return Arrays.stream(node.getChildren())
550 .filter(child -> pattern.matcher(child.getText()).matches())
551 .findFirst();
552 }
553
554 /**
555 * Returns parent node, removing modifier/annotation nodes.
556 *
557 * @param commentBlock child node.
558 * @return parent node.
559 */
560 private static DetailAST getParent(DetailAST commentBlock) {
561 final DetailAST parentNode = commentBlock.getParent();
562 DetailAST result = parentNode;
563 if (result.getType() == TokenTypes.ANNOTATION) {
564 result = parentNode.getParent().getParent();
565 }
566 else if (result.getType() == TokenTypes.MODIFIERS) {
567 result = parentNode.getParent();
568 }
569 return result;
570 }
571
572 /**
573 * Traverse parents until we reach the root node (@code{JavadocTokenTypes.JAVADOC})
574 * child and return its index.
575 *
576 * @param node subtree child node
577 * @return root node child index
578 */
579 public static int getParentIndexOf(DetailNode node) {
580 DetailNode currNode = node;
581 while (currNode.getParent().getIndex() != -1) {
582 currNode = currNode.getParent();
583 }
584 return currNode.getIndex();
585 }
586
587 /**
588 * Get module parent text from paragraph javadoc node.
589 *
590 * @param nodeParagraph paragraph javadoc node
591 * @return parent text
592 */
593 private static String getParentText(DetailNode nodeParagraph) {
594 return getFirstChildOfType(nodeParagraph, JavadocTokenTypes.JAVADOC_INLINE_TAG, 0)
595 .map(JavadocMetadataScraper::getTextFromTag)
596 .orElse(null);
597 }
598
599 /**
600 * Get module type(check/filter/filefilter) based on file name.
601 *
602 * @return module type
603 */
604 private ModuleType getModuleType() {
605 final String simpleModuleName = getModuleSimpleName();
606 final ModuleType result;
607 if (simpleModuleName.endsWith("FileFilter")) {
608 result = ModuleType.FILEFILTER;
609 }
610 else if (simpleModuleName.endsWith("Filter")) {
611 result = ModuleType.FILTER;
612 }
613 else {
614 result = ModuleType.CHECK;
615 }
616 return result;
617 }
618
619 /**
620 * Extract simple file name from the whole file path name.
621 *
622 * @return simple module name
623 */
624 private String getModuleSimpleName() {
625 final String fullFileName = getFilePath();
626 final String[] pathTokens = FILE_SEPARATOR_PATTERN.split(fullFileName);
627 final String fileName = pathTokens[pathTokens.length - 1];
628 return fileName.substring(0, fileName.length() - JAVA_FILE_EXTENSION.length());
629 }
630
631 /**
632 * Retrieve package name of module from the absolute file path.
633 *
634 * @param filePath absolute file path
635 * @return package name
636 */
637 private static String getPackageName(String filePath) {
638 final Deque<String> result = new ArrayDeque<>();
639 final String[] filePathTokens = FILE_SEPARATOR_PATTERN.split(filePath);
640 for (int i = filePathTokens.length - 1; i >= 0; i--) {
641 if ("java".equals(filePathTokens[i]) || "resources".equals(filePathTokens[i])) {
642 break;
643 }
644 result.addFirst(filePathTokens[i]);
645 }
646 final String fileName = result.removeLast();
647 result.addLast(fileName.substring(0, fileName.length() - JAVA_FILE_EXTENSION.length()));
648 return String.join(".", result);
649 }
650
651 /**
652 * Getter method for {@code moduleDetailsStore}.
653 *
654 * @return map containing module details of supplied checks.
655 */
656 public static Map<String, ModuleDetails> getModuleDetailsStore() {
657 return Collections.unmodifiableMap(MODULE_DETAILS_STORE);
658 }
659
660 /** Reset the module detail store of any previous information. */
661 public static void resetModuleDetailsStore() {
662 MODULE_DETAILS_STORE.clear();
663 }
664
665 /**
666 * Check if the current javadoc block comment AST corresponds to the top-level class as we
667 * only want to scrape top-level class javadoc.
668 *
669 * @return true if the current AST corresponds to top level class
670 */
671 private boolean isTopLevelClassJavadoc() {
672 final DetailAST parent = getParent(getBlockCommentAst());
673 final Optional<DetailAST> className = TokenUtil
674 .findFirstTokenByPredicate(parent, child -> {
675 return parent.getType() == TokenTypes.CLASS_DEF
676 && child.getType() == TokenTypes.IDENT;
677 });
678 return className.isPresent()
679 && getModuleSimpleName().equals(className.orElseThrow().getText());
680 }
681
682 /**
683 * Checks whether the paragraph node corresponds to the example section.
684 *
685 * @param ast javadoc paragraph node
686 * @return true if the section matches the example section marker
687 */
688 private static boolean isExamplesText(DetailNode ast) {
689 return isChildNodeTextMatches(ast, EXAMPLES_TAG);
690 }
691
692 /**
693 * Checks whether the list item node is part of a property list.
694 *
695 * @param nodeLi {@code JavadocTokenType.LI} node
696 * @return true if the node is part of a property list
697 */
698 private static boolean isPropertyList(DetailNode nodeLi) {
699 return isChildNodeTextMatches(nodeLi, PROPERTY_TAG);
700 }
701
702 /**
703 * Checks whether the {@code JavadocTokenType.PARAGRAPH} node is referring to the violation
704 * message keys javadoc segment.
705 *
706 * @param nodeParagraph paragraph javadoc node
707 * @return true if paragraph node contains the violation message keys text
708 */
709 private static boolean isViolationMessagesText(DetailNode nodeParagraph) {
710 return isChildNodeTextMatches(nodeParagraph, VIOLATION_MESSAGES_TAG);
711 }
712
713 /**
714 * Checks whether the {@code JavadocTokenType.PARAGRAPH} node is referring to the parent
715 * javadoc segment.
716 *
717 * @param nodeParagraph paragraph javadoc node
718 * @return true if paragraph node contains the parent text
719 */
720 public static boolean isParentText(DetailNode nodeParagraph) {
721 return isChildNodeTextMatches(nodeParagraph, PARENT_TAG);
722 }
723
724 /**
725 * Checks whether the first child {@code JavadocTokenType.TEXT} node matches given pattern.
726 *
727 * @param ast parent javadoc node
728 * @param pattern pattern to match
729 * @return true if one of child text nodes matches pattern
730 */
731 public static boolean isChildNodeTextMatches(DetailNode ast, Pattern pattern) {
732 return getFirstChildOfType(ast, JavadocTokenTypes.TEXT, 0)
733 .map(DetailNode::getText)
734 .map(pattern::matcher)
735 .map(Matcher::matches)
736 .orElse(Boolean.FALSE);
737 }
738}