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.utils;
021
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.TokenTypes;
024
025/**
026 * Utility class that has methods to check javadoc comment position in java file.
027 *
028 */
029public final class BlockCommentPosition {
030
031    /**
032     * Forbid new instances.
033     */
034    private BlockCommentPosition() {
035    }
036
037    /**
038     * Node is on type definition.
039     *
040     * @param blockComment DetailAST
041     * @return true if node is before class, interface, enum or annotation.
042     */
043    public static boolean isOnType(DetailAST blockComment) {
044        return isOnClass(blockComment)
045                || isOnInterface(blockComment)
046                || isOnEnum(blockComment)
047                || isOnAnnotationDef(blockComment)
048                || isOnRecord(blockComment);
049    }
050
051    /**
052     * Node is on class definition.
053     *
054     * @param blockComment DetailAST
055     * @return true if node is before class
056     */
057    public static boolean isOnClass(DetailAST blockComment) {
058        return isOnPlainToken(blockComment, TokenTypes.CLASS_DEF, TokenTypes.LITERAL_CLASS)
059                || isOnTokenWithModifiers(blockComment, TokenTypes.CLASS_DEF)
060                || isOnTokenWithAnnotation(blockComment, TokenTypes.CLASS_DEF);
061    }
062
063    /**
064     * Node is on record definition.
065     *
066     * @param blockComment DetailAST
067     * @return true if node is before class
068     */
069    public static boolean isOnRecord(DetailAST blockComment) {
070        return isOnPlainToken(blockComment, TokenTypes.RECORD_DEF, TokenTypes.LITERAL_RECORD)
071            || isOnTokenWithModifiers(blockComment, TokenTypes.RECORD_DEF)
072            || isOnTokenWithAnnotation(blockComment, TokenTypes.RECORD_DEF);
073    }
074
075    /**
076     * Node is on package definition.
077     *
078     * @param blockComment DetailAST
079     * @return true if node is before package
080     */
081    public static boolean isOnPackage(DetailAST blockComment) {
082        boolean result = isOnTokenWithAnnotation(blockComment, TokenTypes.PACKAGE_DEF);
083
084        if (!result) {
085            DetailAST nextSibling = blockComment.getNextSibling();
086
087            while (nextSibling != null
088                    && nextSibling.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
089                nextSibling = nextSibling.getNextSibling();
090            }
091
092            result = nextSibling != null && nextSibling.getType() == TokenTypes.PACKAGE_DEF;
093        }
094
095        return result;
096    }
097
098    /**
099     * Node is on interface definition.
100     *
101     * @param blockComment DetailAST
102     * @return true if node is before interface
103     */
104    public static boolean isOnInterface(DetailAST blockComment) {
105        return isOnPlainToken(blockComment, TokenTypes.INTERFACE_DEF, TokenTypes.LITERAL_INTERFACE)
106                || isOnTokenWithModifiers(blockComment, TokenTypes.INTERFACE_DEF)
107                || isOnTokenWithAnnotation(blockComment, TokenTypes.INTERFACE_DEF);
108    }
109
110    /**
111     * Node is on enum definition.
112     *
113     * @param blockComment DetailAST
114     * @return true if node is before enum
115     */
116    public static boolean isOnEnum(DetailAST blockComment) {
117        return isOnPlainToken(blockComment, TokenTypes.ENUM_DEF, TokenTypes.ENUM)
118                || isOnTokenWithModifiers(blockComment, TokenTypes.ENUM_DEF)
119                || isOnTokenWithAnnotation(blockComment, TokenTypes.ENUM_DEF);
120    }
121
122    /**
123     * Node is on annotation definition.
124     *
125     * @param blockComment DetailAST
126     * @return true if node is before annotation
127     */
128    public static boolean isOnAnnotationDef(DetailAST blockComment) {
129        return isOnPlainToken(blockComment, TokenTypes.ANNOTATION_DEF, TokenTypes.AT)
130                || isOnTokenWithModifiers(blockComment, TokenTypes.ANNOTATION_DEF)
131                || isOnTokenWithAnnotation(blockComment, TokenTypes.ANNOTATION_DEF);
132    }
133
134    /**
135     * Node is on type member declaration.
136     *
137     * @param blockComment DetailAST
138     * @return true if node is before method, field, constructor, enum constant
139     *     or annotation field
140     */
141    public static boolean isOnMember(DetailAST blockComment) {
142        return isOnMethod(blockComment)
143                || isOnField(blockComment)
144                || isOnConstructor(blockComment)
145                || isOnEnumConstant(blockComment)
146                || isOnAnnotationField(blockComment)
147                || isOnCompactConstructor(blockComment);
148    }
149
150    /**
151     * Node is on method declaration.
152     *
153     * @param blockComment DetailAST
154     * @return true if node is before method
155     */
156    public static boolean isOnMethod(DetailAST blockComment) {
157        return isOnPlainClassMember(blockComment)
158                || isOnTokenWithModifiers(blockComment, TokenTypes.METHOD_DEF)
159                || isOnTokenWithAnnotation(blockComment, TokenTypes.METHOD_DEF);
160    }
161
162    /**
163     * Node is on field declaration.
164     *
165     * @param blockComment DetailAST
166     * @return true if node is before field
167     */
168    public static boolean isOnField(DetailAST blockComment) {
169        return isOnPlainClassMember(blockComment)
170                || isOnTokenWithModifiers(blockComment, TokenTypes.VARIABLE_DEF)
171                    && blockComment.getParent().getParent().getParent()
172                        .getType() == TokenTypes.OBJBLOCK
173                || isOnTokenWithAnnotation(blockComment, TokenTypes.VARIABLE_DEF)
174                    && blockComment.getParent().getParent().getParent()
175                        .getParent().getType() == TokenTypes.OBJBLOCK;
176    }
177
178    /**
179     * Node is on constructor.
180     *
181     * @param blockComment DetailAST
182     * @return true if node is before constructor
183     */
184    public static boolean isOnConstructor(DetailAST blockComment) {
185        return isOnPlainToken(blockComment, TokenTypes.CTOR_DEF, TokenTypes.IDENT)
186                || isOnTokenWithModifiers(blockComment, TokenTypes.CTOR_DEF)
187                || isOnTokenWithAnnotation(blockComment, TokenTypes.CTOR_DEF)
188                || isOnPlainClassMember(blockComment);
189    }
190
191    /**
192     * Node is on compact constructor, note that we don't need to check for a plain
193     * token here, since a compact constructor must be public.
194     *
195     * @param blockComment DetailAST
196     * @return true if node is before compact constructor
197     */
198    public static boolean isOnCompactConstructor(DetailAST blockComment) {
199        return isOnPlainToken(blockComment, TokenTypes.COMPACT_CTOR_DEF, TokenTypes.IDENT)
200                || isOnTokenWithModifiers(blockComment, TokenTypes.COMPACT_CTOR_DEF)
201                || isOnTokenWithAnnotation(blockComment, TokenTypes.COMPACT_CTOR_DEF);
202    }
203
204    /**
205     * Node is on enum constant.
206     *
207     * @param blockComment DetailAST
208     * @return true if node is before enum constant
209     */
210    public static boolean isOnEnumConstant(DetailAST blockComment) {
211        final DetailAST parent = blockComment.getParent();
212        boolean result = false;
213        if (parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
214            final DetailAST prevSibling = getPrevSiblingSkipComments(blockComment);
215            if (prevSibling.getType() == TokenTypes.ANNOTATIONS && !prevSibling.hasChildren()) {
216                result = true;
217            }
218        }
219        else if (parent.getType() == TokenTypes.ANNOTATION
220                && parent.getParent().getParent().getType() == TokenTypes.ENUM_CONSTANT_DEF) {
221            result = true;
222        }
223
224        return result;
225    }
226
227    /**
228     * Node is on annotation field declaration.
229     *
230     * @param blockComment DetailAST
231     * @return true if node is before annotation field
232     */
233    public static boolean isOnAnnotationField(DetailAST blockComment) {
234        return isOnPlainClassMember(blockComment)
235                || isOnTokenWithModifiers(blockComment, TokenTypes.ANNOTATION_FIELD_DEF)
236                || isOnTokenWithAnnotation(blockComment, TokenTypes.ANNOTATION_FIELD_DEF);
237    }
238
239    /**
240     * Checks that block comment is on specified token without any modifiers.
241     *
242     * @param blockComment block comment start DetailAST
243     * @param parentTokenType parent token type
244     * @param nextTokenType next token type
245     * @return true if block comment is on specified token without modifiers
246     */
247    private static boolean isOnPlainToken(DetailAST blockComment,
248            int parentTokenType, int nextTokenType) {
249        return blockComment.getParent().getType() == parentTokenType
250                && !getPrevSiblingSkipComments(blockComment).hasChildren()
251                && getNextSiblingSkipComments(blockComment).getType() == nextTokenType;
252    }
253
254    /**
255     * Checks that block comment is on specified token with modifiers.
256     *
257     * @param blockComment block comment start DetailAST
258     * @param tokenType parent token type
259     * @return true if block comment is on specified token with modifiers
260     */
261    private static boolean isOnTokenWithModifiers(DetailAST blockComment, int tokenType) {
262        return blockComment.getParent().getType() == TokenTypes.MODIFIERS
263                && blockComment.getParent().getParent().getType() == tokenType
264                && getPrevSiblingSkipComments(blockComment) == null;
265    }
266
267    /**
268     * Checks that block comment is on specified token with annotation.
269     *
270     * @param blockComment block comment start DetailAST
271     * @param tokenType parent token type
272     * @return true if block comment is on specified token with annotation
273     */
274    private static boolean isOnTokenWithAnnotation(DetailAST blockComment, int tokenType) {
275        return blockComment.getParent().getType() == TokenTypes.ANNOTATION
276                && getPrevSiblingSkipComments(blockComment.getParent()) == null
277                && blockComment.getParent().getParent().getParent().getType() == tokenType
278                && getPrevSiblingSkipComments(blockComment) == null;
279    }
280
281    /**
282     * Checks that block comment is on specified class member without any modifiers.
283     *
284     * @param blockComment block comment start DetailAST
285     * @return true if block comment is on specified token without modifiers
286     */
287    private static boolean isOnPlainClassMember(DetailAST blockComment) {
288        DetailAST parent = blockComment.getParent();
289        // type could be in fully qualified form, so we go up to Type token
290        while (parent.getType() == TokenTypes.DOT) {
291            parent = parent.getParent();
292        }
293        return (parent.getType() == TokenTypes.TYPE
294                    || parent.getType() == TokenTypes.TYPE_PARAMETERS)
295                // previous parent sibling is always TokenTypes.MODIFIERS
296                && !parent.getPreviousSibling().hasChildren()
297                && parent.getParent().getParent().getType() == TokenTypes.OBJBLOCK;
298    }
299
300    /**
301     * Get next sibling node skipping any comment nodes.
302     *
303     * @param node current node
304     * @return next sibling
305     */
306    private static DetailAST getNextSiblingSkipComments(DetailAST node) {
307        DetailAST result = node;
308        while (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
309                || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
310            result = result.getNextSibling();
311        }
312        return result;
313    }
314
315    /**
316     * Get previous sibling node skipping any comments.
317     *
318     * @param node current node
319     * @return previous sibling
320     */
321    private static DetailAST getPrevSiblingSkipComments(DetailAST node) {
322        DetailAST result = node.getPreviousSibling();
323        while (result != null
324                && (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
325                || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN)) {
326            result = result.getPreviousSibling();
327        }
328        return result;
329    }
330
331}