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