001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2021 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, TokenTypes.METHOD_DEF)
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, TokenTypes.VARIABLE_DEF)
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    }
189
190    /**
191     * Node is on compact constructor, note that we don't need to check for a plain
192     * token here, since a compact constructor must be public.
193     *
194     * @param blockComment DetailAST
195     * @return true if node is before compact constructor
196     */
197    public static boolean isOnCompactConstructor(DetailAST blockComment) {
198        return isOnTokenWithModifiers(blockComment, TokenTypes.COMPACT_CTOR_DEF)
199                || isOnTokenWithAnnotation(blockComment, TokenTypes.COMPACT_CTOR_DEF);
200    }
201
202    /**
203     * Node is on enum constant.
204     *
205     * @param blockComment DetailAST
206     * @return true if node is before enum constant
207     */
208    public static boolean isOnEnumConstant(DetailAST blockComment) {
209        final DetailAST parent = blockComment.getParent();
210        boolean result = false;
211        if (parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
212            final DetailAST prevSibling = getPrevSiblingSkipComments(blockComment);
213            if (prevSibling.getType() == TokenTypes.ANNOTATIONS && !prevSibling.hasChildren()) {
214                result = true;
215            }
216        }
217        else if (parent.getType() == TokenTypes.ANNOTATION
218                && parent.getParent().getParent().getType() == TokenTypes.ENUM_CONSTANT_DEF) {
219            result = true;
220        }
221
222        return result;
223    }
224
225    /**
226     * Node is on annotation field declaration.
227     *
228     * @param blockComment DetailAST
229     * @return true if node is before annotation field
230     */
231    public static boolean isOnAnnotationField(DetailAST blockComment) {
232        return isOnPlainClassMember(blockComment, TokenTypes.ANNOTATION_FIELD_DEF)
233                || isOnTokenWithModifiers(blockComment, TokenTypes.ANNOTATION_FIELD_DEF)
234                || isOnTokenWithAnnotation(blockComment, TokenTypes.ANNOTATION_FIELD_DEF);
235    }
236
237    /**
238     * Checks that block comment is on specified token without any modifiers.
239     *
240     * @param blockComment block comment start DetailAST
241     * @param parentTokenType parent token type
242     * @param nextTokenType next token type
243     * @return true if block comment is on specified token without modifiers
244     */
245    private static boolean isOnPlainToken(DetailAST blockComment,
246            int parentTokenType, int nextTokenType) {
247        return blockComment.getParent().getType() == parentTokenType
248                && !getPrevSiblingSkipComments(blockComment).hasChildren()
249                && getNextSiblingSkipComments(blockComment).getType() == nextTokenType;
250    }
251
252    /**
253     * Checks that block comment is on specified token with modifiers.
254     *
255     * @param blockComment block comment start DetailAST
256     * @param tokenType parent token type
257     * @return true if block comment is on specified token with modifiers
258     */
259    private static boolean isOnTokenWithModifiers(DetailAST blockComment, int tokenType) {
260        return blockComment.getParent().getType() == TokenTypes.MODIFIERS
261                && blockComment.getParent().getParent().getType() == tokenType
262                && getPrevSiblingSkipComments(blockComment) == null;
263    }
264
265    /**
266     * Checks that block comment is on specified token with annotation.
267     *
268     * @param blockComment block comment start DetailAST
269     * @param tokenType parent token type
270     * @return true if block comment is on specified token with annotation
271     */
272    private static boolean isOnTokenWithAnnotation(DetailAST blockComment, int tokenType) {
273        return blockComment.getParent().getType() == TokenTypes.ANNOTATION
274                && getPrevSiblingSkipComments(blockComment.getParent()) == null
275                && blockComment.getParent().getParent().getParent().getType() == tokenType
276                && getPrevSiblingSkipComments(blockComment) == null;
277    }
278
279    /**
280     * Checks that block comment is on specified class member without any modifiers.
281     *
282     * @param blockComment block comment start DetailAST
283     * @param memberType parent token type
284     * @return true if block comment is on specified token without modifiers
285     */
286    private static boolean isOnPlainClassMember(DetailAST blockComment, int memberType) {
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                && parent.getParent().getType() == memberType
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.getNextSibling();
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}