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