001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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 isOnTokenWithModifiers(blockComment, TokenTypes.COMPACT_CTOR_DEF)
200                || isOnTokenWithAnnotation(blockComment, TokenTypes.COMPACT_CTOR_DEF);
201    }
202
203    /**
204     * Node is on enum constant.
205     *
206     * @param blockComment DetailAST
207     * @return true if node is before enum constant
208     */
209    public static boolean isOnEnumConstant(DetailAST blockComment) {
210        final DetailAST parent = blockComment.getParent();
211        boolean result = false;
212        if (parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
213            final DetailAST prevSibling = getPrevSiblingSkipComments(blockComment);
214            if (prevSibling.getType() == TokenTypes.ANNOTATIONS && !prevSibling.hasChildren()) {
215                result = true;
216            }
217        }
218        else if (parent.getType() == TokenTypes.ANNOTATION
219                && parent.getParent().getParent().getType() == TokenTypes.ENUM_CONSTANT_DEF) {
220            result = true;
221        }
222
223        return result;
224    }
225
226    /**
227     * Node is on annotation field declaration.
228     *
229     * @param blockComment DetailAST
230     * @return true if node is before annotation field
231     */
232    public static boolean isOnAnnotationField(DetailAST blockComment) {
233        return isOnPlainClassMember(blockComment)
234                || isOnTokenWithModifiers(blockComment, TokenTypes.ANNOTATION_FIELD_DEF)
235                || isOnTokenWithAnnotation(blockComment, TokenTypes.ANNOTATION_FIELD_DEF);
236    }
237
238    /**
239     * Checks that block comment is on specified token without any modifiers.
240     *
241     * @param blockComment block comment start DetailAST
242     * @param parentTokenType parent token type
243     * @param nextTokenType next token type
244     * @return true if block comment is on specified token without modifiers
245     */
246    private static boolean isOnPlainToken(DetailAST blockComment,
247            int parentTokenType, int nextTokenType) {
248        return blockComment.getParent().getType() == parentTokenType
249                && !getPrevSiblingSkipComments(blockComment).hasChildren()
250                && getNextSiblingSkipComments(blockComment).getType() == nextTokenType;
251    }
252
253    /**
254     * Checks that block comment is on specified token with modifiers.
255     *
256     * @param blockComment block comment start DetailAST
257     * @param tokenType parent token type
258     * @return true if block comment is on specified token with modifiers
259     */
260    private static boolean isOnTokenWithModifiers(DetailAST blockComment, int tokenType) {
261        return blockComment.getParent().getType() == TokenTypes.MODIFIERS
262                && blockComment.getParent().getParent().getType() == tokenType
263                && getPrevSiblingSkipComments(blockComment) == null;
264    }
265
266    /**
267     * Checks that block comment is on specified token with annotation.
268     *
269     * @param blockComment block comment start DetailAST
270     * @param tokenType parent token type
271     * @return true if block comment is on specified token with annotation
272     */
273    private static boolean isOnTokenWithAnnotation(DetailAST blockComment, int tokenType) {
274        return blockComment.getParent().getType() == TokenTypes.ANNOTATION
275                && getPrevSiblingSkipComments(blockComment.getParent()) == null
276                && blockComment.getParent().getParent().getParent().getType() == tokenType
277                && getPrevSiblingSkipComments(blockComment) == null;
278    }
279
280    /**
281     * Checks that block comment is on specified class member without any modifiers.
282     *
283     * @param blockComment block comment start DetailAST
284     * @return true if block comment is on specified token without modifiers
285     */
286    private static boolean isOnPlainClassMember(DetailAST blockComment) {
287        DetailAST parent = blockComment.getParent();
288        // type could be in fully qualified form, so we go up to Type token
289        while (parent.getType() == TokenTypes.DOT) {
290            parent = parent.getParent();
291        }
292        return (parent.getType() == TokenTypes.TYPE
293                    || parent.getType() == TokenTypes.TYPE_PARAMETERS)
294                // previous parent sibling is always TokenTypes.MODIFIERS
295                && !parent.getPreviousSibling().hasChildren()
296                && parent.getParent().getParent().getType() == TokenTypes.OBJBLOCK;
297    }
298
299    /**
300     * Get next sibling node skipping any comment nodes.
301     *
302     * @param node current node
303     * @return next sibling
304     */
305    private static DetailAST getNextSiblingSkipComments(DetailAST node) {
306        DetailAST result = node;
307        while (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
308                || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
309            result = result.getNextSibling();
310        }
311        return result;
312    }
313
314    /**
315     * Get previous sibling node skipping any comments.
316     *
317     * @param node current node
318     * @return previous sibling
319     */
320    private static DetailAST getPrevSiblingSkipComments(DetailAST node) {
321        DetailAST result = node.getPreviousSibling();
322        while (result != null
323                && (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
324                || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN)) {
325            result = result.getPreviousSibling();
326        }
327        return result;
328    }
329
330}