001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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.utils;
021
022import java.util.ArrayList;
023import java.util.List;
024import java.util.Map;
025import java.util.regex.Pattern;
026
027import javax.annotation.Nullable;
028
029import com.puppycrawl.tools.checkstyle.api.DetailAST;
030import com.puppycrawl.tools.checkstyle.api.DetailNode;
031import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
032import com.puppycrawl.tools.checkstyle.api.TokenTypes;
033
034/**
035 * Contains utility methods for working with Javadoc.
036 */
037public final class JavadocUtil {
038
039    /** Maps from a token name to value. */
040    private static final Map<String, Integer> TOKEN_NAME_TO_VALUE;
041    /** Maps from a token value to name. */
042    private static final Map<Integer, String> TOKEN_VALUE_TO_NAME;
043
044    /** Exception message for unknown JavaDoc token id. */
045    private static final String UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE = "Unknown javadoc"
046            + " token id. Given id: ";
047
048    /** Newline pattern. */
049    private static final Pattern NEWLINE = Pattern.compile("\n");
050
051    /** Return pattern. */
052    private static final Pattern RETURN = Pattern.compile("\r");
053
054    /** Tab pattern. */
055    private static final Pattern TAB = Pattern.compile("\t");
056
057    // initialise the constants
058    static {
059        TOKEN_NAME_TO_VALUE =
060                TokenUtil.nameToValueMapFromPublicIntFields(JavadocCommentsTokenTypes.class);
061        TOKEN_VALUE_TO_NAME = TokenUtil.invertMap(TOKEN_NAME_TO_VALUE);
062    }
063
064    /** Prevent instantiation. */
065    private JavadocUtil() {
066    }
067
068    /**
069     * Checks that commentContent starts with '*' javadoc comment identifier.
070     *
071     * @param commentContent
072     *        content of block comment
073     * @return true if commentContent starts with '*' javadoc comment
074     *         identifier.
075     */
076    public static boolean isJavadocComment(String commentContent) {
077        boolean result = false;
078
079        if (!commentContent.isEmpty()) {
080            final char docCommentIdentifier = commentContent.charAt(0);
081            result = docCommentIdentifier == '*';
082        }
083
084        return result;
085    }
086
087    /**
088     * Checks block comment content starts with '*' javadoc comment identifier.
089     *
090     * @param blockCommentBegin
091     *        block comment AST
092     * @return true if block comment content starts with '*' javadoc comment
093     *         identifier.
094     */
095    public static boolean isJavadocComment(DetailAST blockCommentBegin) {
096        final String commentContent = getBlockCommentContent(blockCommentBegin);
097        return isJavadocComment(commentContent) && isCorrectJavadocPosition(blockCommentBegin);
098    }
099
100    /**
101     * Gets content of block comment.
102     *
103     * @param blockCommentBegin
104     *        block comment AST.
105     * @return content of block comment.
106     */
107    public static String getBlockCommentContent(DetailAST blockCommentBegin) {
108        final DetailAST commentContent = blockCommentBegin.getFirstChild();
109        return commentContent.getText();
110    }
111
112    /**
113     * Get content of Javadoc comment.
114     *
115     * @param javadocCommentBegin
116     *        Javadoc comment AST
117     * @return content of Javadoc comment.
118     */
119    public static String getJavadocCommentContent(DetailAST javadocCommentBegin) {
120        final DetailAST commentContent = javadocCommentBegin.getFirstChild();
121        return commentContent.getText().substring(1);
122    }
123
124    /**
125     * Returns the Javadoc block comment attached to the given declaration AST node.
126     *
127     * @param ast the declaration AST node
128     * @return the attached Javadoc block comment, or {@code null} if none is found
129     */
130    @Nullable
131    public static DetailAST getAttachedJavadocComment(final DetailAST ast) {
132        DetailAST result = null;
133        DetailAST child = ast.getFirstChild();
134        while (result == null && child.getType() != TokenTypes.IDENT) {
135            result = findJavadocComment(child);
136            child = child.getNextSibling();
137        }
138        return result;
139    }
140
141    /**
142     * Finds the first Javadoc block comment under the given AST node.
143     *
144     * @param ast the AST node to search
145     * @return the Javadoc block comment, or {@code null} if none is found
146     */
147    @Nullable
148    private static DetailAST findJavadocComment(DetailAST ast) {
149        DetailAST result = null;
150        if (ast.getType() == TokenTypes.BLOCK_COMMENT_BEGIN && isJavadocComment(ast)) {
151            result = ast;
152        }
153        else {
154            DetailAST child = ast.getFirstChild();
155            while (result == null && child != null) {
156                result = findJavadocComment(child);
157                child = child.getNextSibling();
158            }
159        }
160        return result;
161    }
162
163    /**
164     * Returns the first child token that has a specified type.
165     *
166     * @param detailNode
167     *        Javadoc AST node
168     * @param type
169     *        the token type to match
170     * @return the matching token, or null if no match
171     */
172    public static DetailNode findFirstToken(DetailNode detailNode, int type) {
173        DetailNode returnValue = null;
174        DetailNode node = detailNode.getFirstChild();
175        while (node != null) {
176            if (node.getType() == type) {
177                returnValue = node;
178                break;
179            }
180            node = node.getNextSibling();
181        }
182        return returnValue;
183    }
184
185    /**
186     * Returns all child tokens that have a specified type.
187     *
188     * @param detailNode Javadoc AST node
189     * @param type the token type to match
190     * @return the matching tokens, or an empty list if no match
191     */
192    public static List<DetailNode> getAllNodesOfType(DetailNode detailNode, int type) {
193        final List<DetailNode> nodes = new ArrayList<>();
194        DetailNode node = detailNode.getFirstChild();
195        while (node != null) {
196            if (node.getType() == type) {
197                nodes.add(node);
198            }
199            node = node.getNextSibling();
200        }
201        return nodes;
202    }
203
204    /**
205     * Checks whether the given AST node is an HTML element with the specified tag name.
206     * This method ignore void elements.
207     *
208     * @param ast the AST node to check
209     *            (must be of type {@link JavadocCommentsTokenTypes#HTML_ELEMENT})
210     * @param expectedTagName the tag name to match (case-insensitive)
211     * @return {@code true} if the node has the given tag name, {@code false} otherwise
212     */
213    public static boolean isTag(DetailNode ast, String expectedTagName) {
214        final DetailNode htmlTagStart = findFirstToken(ast,
215                JavadocCommentsTokenTypes.HTML_TAG_START);
216        boolean isTag = false;
217        if (htmlTagStart != null) {
218            final String tagName = findFirstToken(htmlTagStart,
219                JavadocCommentsTokenTypes.TAG_NAME).getText();
220            isTag = expectedTagName.equalsIgnoreCase(tagName);
221        }
222        return isTag;
223    }
224
225    /**
226     * Gets next sibling of specified node with the specified type.
227     *
228     * @param node DetailNode
229     * @param tokenType javadoc token type
230     * @return next sibling.
231     */
232    public static DetailNode getNextSibling(DetailNode node, int tokenType) {
233        DetailNode nextSibling = node.getNextSibling();
234        while (nextSibling != null && nextSibling.getType() != tokenType) {
235            nextSibling = nextSibling.getNextSibling();
236        }
237        return nextSibling;
238    }
239
240    /**
241     * Returns the name of a token for a given ID.
242     *
243     * @param id
244     *        the ID of the token name to get
245     * @return a token name
246     * @throws IllegalArgumentException if an unknown token ID was specified.
247     */
248    public static String getTokenName(int id) {
249        final String name = TOKEN_VALUE_TO_NAME.get(id);
250        if (name == null) {
251            throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id);
252        }
253        return name;
254    }
255
256    /**
257     * Returns the ID of a token for a given name.
258     *
259     * @param name
260     *        the name of the token ID to get
261     * @return a token ID
262     * @throws IllegalArgumentException if an unknown token name was specified.
263     */
264    public static int getTokenId(String name) {
265        final Integer id = TOKEN_NAME_TO_VALUE.get(name);
266        if (id == null) {
267            throw new IllegalArgumentException("Unknown javadoc token name. Given name " + name);
268        }
269        return id;
270    }
271
272    /**
273     * Extracts the tag name from the given Javadoc tag section.
274     *
275     * @param javadocTagSection the node representing a Javadoc tag section.
276     *       This node must be of type {@link JavadocCommentsTokenTypes#JAVADOC_BLOCK_TAG}
277     *       or {@link JavadocCommentsTokenTypes#JAVADOC_INLINE_TAG}.
278     *  @return the tag name (e.g., "param", "return", "link")
279     */
280    public static String getTagName(DetailNode javadocTagSection) {
281        return findFirstToken(javadocTagSection.getFirstChild(),
282                    JavadocCommentsTokenTypes.TAG_NAME).getText();
283    }
284
285    /**
286     * Replace all control chars with escaped symbols.
287     *
288     * @param text the String to process.
289     * @return the processed String with all control chars escaped.
290     */
291    public static String escapeAllControlChars(String text) {
292        final String textWithoutNewlines = NEWLINE.matcher(text).replaceAll("\\\\n");
293        final String textWithoutReturns = RETURN.matcher(textWithoutNewlines).replaceAll("\\\\r");
294        return TAB.matcher(textWithoutReturns).replaceAll("\\\\t");
295    }
296
297    /**
298     * Checks Javadoc comment it's in right place.
299     *
300     * <p>From Javadoc util documentation:
301     * "Placement of comments - Documentation comments are recognized only when placed
302     * immediately before class, interface, constructor, method, field or annotation field
303     * declarations -- see the class example, method example, and field example.
304     * Documentation comments placed in the body of a method are ignored."</p>
305     *
306     * <p>If there are many documentation comments per declaration statement,
307     * only the last one will be recognized.</p>
308     *
309     * @param blockComment Block comment AST
310     * @return true if Javadoc is in right place
311     * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/unix/javadoc.html">
312     *     Javadoc util documentation</a>
313     */
314    public static boolean isCorrectJavadocPosition(DetailAST blockComment) {
315        // We must be sure that after this one there are no other documentation comments.
316        DetailAST sibling = blockComment.getNextSibling();
317        while (sibling != null) {
318            if (sibling.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
319                if (isJavadocComment(getBlockCommentContent(sibling))) {
320                    // Found another javadoc comment, so this one should be ignored.
321                    break;
322                }
323                sibling = sibling.getNextSibling();
324            }
325            else if (sibling.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
326                sibling = sibling.getNextSibling();
327            }
328            else {
329                // Annotation, declaration or modifier is here. Do not check further.
330                sibling = null;
331            }
332        }
333        return sibling == null
334            && (BlockCommentPosition.isOnType(blockComment)
335                || BlockCommentPosition.isOnMember(blockComment)
336                || BlockCommentPosition.isOnPackage(blockComment)
337                || BlockCommentPosition.isOnModule(blockComment));
338    }
339
340}