1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2025 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ///////////////////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.meta;
21
22 import java.util.Optional;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25
26 import com.puppycrawl.tools.checkstyle.api.DetailNode;
27 import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
28 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
29
30 /**
31 * Class for scraping module metadata from the corresponding class' class-level javadoc.
32 */
33 public final class JavadocMetadataScraperUtil {
34
35 /** Regular expression for detecting ANTLR tokens(for e.g. CLASS_DEF). */
36 private static final Pattern TOKEN_TEXT_PATTERN = Pattern.compile("([A-Z_]{2,})+");
37
38 /**
39 * Private utility constructor.
40 */
41 private JavadocMetadataScraperUtil() {
42 }
43
44 /**
45 * Performs a depth-first traversal of the subtree starting at {@code startNode}
46 * and ending at {@code endNode}, and constructs the concatenated text of all nodes
47 * in that range, ignoring {@code JavadocToken} texts.
48 *
49 * @param startNode the node where traversal begins (inclusive)
50 * @param endNode the node where traversal ends (inclusive)
51 * @return the constructed text from the specified subtree range
52 */
53 public static String constructSubTreeText(DetailNode startNode,
54 DetailNode endNode) {
55 DetailNode curNode = startNode;
56 final StringBuilder result = new StringBuilder(1024);
57
58 while (curNode != null) {
59 if (isContentToWrite(curNode)) {
60 String childText = curNode.getText();
61
62 if (isInsideCodeInlineTag(curNode)) {
63 childText = adjustCodeInlineTagChildToHtml(curNode);
64 }
65
66 result.append(childText);
67 }
68
69 DetailNode toVisit = curNode.getFirstChild();
70 while (curNode != endNode && toVisit == null) {
71 toVisit = curNode.getNextSibling();
72 curNode = curNode.getParent();
73 }
74
75 curNode = toVisit;
76 }
77 return result.toString().trim();
78 }
79
80 /**
81 * Checks whether the given node is inside a {@code @code} Javadoc inline tag.
82 *
83 * @param node the node to check
84 * @return true if the node is inside a {@code @code} inline tag, false otherwise
85 */
86 private static boolean isInsideCodeInlineTag(DetailNode node) {
87 return node.getParent() != null
88 && node.getParent().getType() == JavadocCommentsTokenTypes.CODE_INLINE_TAG;
89 }
90
91 /**
92 * Checks whether selected Javadoc node is considered as something to write.
93 *
94 * @param detailNode javadoc node to check.
95 * @return whether javadoc node is something to write.
96 */
97 private static boolean isContentToWrite(DetailNode detailNode) {
98
99 return detailNode.getType() != JavadocCommentsTokenTypes.LEADING_ASTERISK
100 && (detailNode.getType() == JavadocCommentsTokenTypes.TEXT
101 || !TOKEN_TEXT_PATTERN.matcher(detailNode.getText()).matches());
102 }
103
104 /**
105 * Adjusts certain child of {@code @code} Javadoc inline tag to its analogous html format.
106 *
107 * @param codeChild {@code @code} child to convert.
108 * @return converted {@code @code} child element, otherwise just the original text.
109 */
110 public static String adjustCodeInlineTagChildToHtml(DetailNode codeChild) {
111
112 return switch (codeChild.getType()) {
113 case JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG_END -> "</code>";
114 case JavadocCommentsTokenTypes.TAG_NAME -> "";
115 case JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG_START -> "<code>";
116 default -> codeChild.getText().trim();
117 };
118 }
119
120 /**
121 * Returns the first child node of the given parent that matches the provided {@code tokenType}.
122 *
123 * @param node the parent node
124 * @param tokenType the token type to match
125 * @return an {@link Optional} containing the first matching child node,
126 * or an empty {@link Optional} if none is found
127 */
128 private static Optional<DetailNode> getFirstChildOfType(DetailNode node, int tokenType) {
129 return JavadocUtil.getAllNodesOfType(node, tokenType).stream().findFirst();
130 }
131
132 /**
133 * Checks whether the first child {@code JavadocTokenType.TEXT} node matches given pattern.
134 *
135 * @param ast parent javadoc node
136 * @param pattern pattern to match
137 * @return true if one of child text nodes matches pattern
138 */
139 public static boolean isChildNodeTextMatches(DetailNode ast, Pattern pattern) {
140 return getFirstChildOfType(ast, JavadocCommentsTokenTypes.TEXT)
141 .map(DetailNode::getText)
142 .map(pattern::matcher)
143 .map(Matcher::matches)
144 .orElse(Boolean.FALSE);
145 }
146 }