001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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 != null) {
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, TokenTypes.ANNOTATION_FIELD_DEF)
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() != null
249                && 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() != null
263                && blockComment.getParent().getType() == TokenTypes.MODIFIERS
264                && blockComment.getParent().getParent().getType() == tokenType
265                && getPrevSiblingSkipComments(blockComment) == null;
266    }
267
268    /**
269     * Checks that block comment is on specified token with annotation.
270     *
271     * @param blockComment block comment start DetailAST
272     * @param tokenType parent token type
273     * @return true if block comment is on specified token with annotation
274     */
275    private static boolean isOnTokenWithAnnotation(DetailAST blockComment, int tokenType) {
276        return blockComment.getParent() != null
277                && blockComment.getParent().getType() == TokenTypes.ANNOTATION
278                && getPrevSiblingSkipComments(blockComment.getParent()) == null
279                && blockComment.getParent().getParent().getParent().getType() == tokenType
280                && getPrevSiblingSkipComments(blockComment) == null;
281    }
282
283    /**
284     * Checks that block comment is on specified class member without any modifiers.
285     *
286     * @param blockComment block comment start DetailAST
287     * @param memberType parent token type
288     * @return true if block comment is on specified token without modifiers
289     */
290    private static boolean isOnPlainClassMember(DetailAST blockComment, int memberType) {
291        DetailAST parent = blockComment.getParent();
292        // type could be in fully qualified form, so we go up to Type token
293        while (parent != null && (parent.getType() == TokenTypes.DOT
294                || parent.getType() == TokenTypes.ARRAY_DECLARATOR)) {
295            parent = parent.getParent();
296        }
297        return parent != null
298                && (parent.getType() == TokenTypes.TYPE
299                    || parent.getType() == TokenTypes.TYPE_PARAMETERS)
300                && parent.getParent().getType() == memberType
301                // previous parent sibling is always TokenTypes.MODIFIERS
302                && !parent.getPreviousSibling().hasChildren()
303                && parent.getParent().getParent().getType() == TokenTypes.OBJBLOCK;
304    }
305
306    /**
307     * Get next sibling node skipping any comment nodes.
308     *
309     * @param node current node
310     * @return next sibling
311     */
312    private static DetailAST getNextSiblingSkipComments(DetailAST node) {
313        DetailAST result = node.getNextSibling();
314        while (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
315                || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
316            result = result.getNextSibling();
317        }
318        return result;
319    }
320
321    /**
322     * Get previous sibling node skipping any comments.
323     *
324     * @param node current node
325     * @return previous sibling
326     */
327    private static DetailAST getPrevSiblingSkipComments(DetailAST node) {
328        DetailAST result = node.getPreviousSibling();
329        while (result != null
330                && (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
331                || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN)) {
332            result = result.getPreviousSibling();
333        }
334        return result;
335    }
336
337}