001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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 interface definition.
062     * @param blockComment DetailAST
063     * @return true if node is before interface
064     */
065    public static boolean isOnInterface(DetailAST blockComment) {
066        return isOnPlainToken(blockComment, TokenTypes.INTERFACE_DEF, TokenTypes.LITERAL_INTERFACE)
067                || isOnTokenWithModifiers(blockComment, TokenTypes.INTERFACE_DEF)
068                || isOnTokenWithAnnotation(blockComment, TokenTypes.INTERFACE_DEF);
069    }
070
071    /**
072     * Node is on enum definition.
073     * @param blockComment DetailAST
074     * @return true if node is before enum
075     */
076    public static boolean isOnEnum(DetailAST blockComment) {
077        return isOnPlainToken(blockComment, TokenTypes.ENUM_DEF, TokenTypes.ENUM)
078                || isOnTokenWithModifiers(blockComment, TokenTypes.ENUM_DEF)
079                || isOnTokenWithAnnotation(blockComment, TokenTypes.ENUM_DEF);
080    }
081
082    /**
083     * Node is on annotation definition.
084     * @param blockComment DetailAST
085     * @return true if node is before annotation
086     */
087    public static boolean isOnAnnotationDef(DetailAST blockComment) {
088        return isOnPlainToken(blockComment, TokenTypes.ANNOTATION_DEF, TokenTypes.AT)
089                || isOnTokenWithModifiers(blockComment, TokenTypes.ANNOTATION_DEF)
090                || isOnTokenWithAnnotation(blockComment, TokenTypes.ANNOTATION_DEF);
091    }
092
093    /**
094     * Node is on type member declaration.
095     * @param blockComment DetailAST
096     * @return true if node is before method, field, constructor, enum constant
097     *     or annotation field
098     */
099    public static boolean isOnMember(DetailAST blockComment) {
100        return isOnMethod(blockComment)
101                || isOnField(blockComment)
102                || isOnConstructor(blockComment)
103                || isOnEnumConstant(blockComment)
104                || isOnAnnotationField(blockComment);
105    }
106
107    /**
108     * Node is on method declaration.
109     * @param blockComment DetailAST
110     * @return true if node is before method
111     */
112    public static boolean isOnMethod(DetailAST blockComment) {
113        return isOnPlainClassMember(blockComment, TokenTypes.METHOD_DEF)
114                || isOnTokenWithModifiers(blockComment, TokenTypes.METHOD_DEF)
115                || isOnTokenWithAnnotation(blockComment, TokenTypes.METHOD_DEF);
116    }
117
118    /**
119     * Node is on field declaration.
120     * @param blockComment DetailAST
121     * @return true if node is before field
122     */
123    public static boolean isOnField(DetailAST blockComment) {
124        return isOnPlainClassMember(blockComment, TokenTypes.VARIABLE_DEF)
125                || isOnTokenWithModifiers(blockComment, TokenTypes.VARIABLE_DEF)
126                || isOnTokenWithAnnotation(blockComment, TokenTypes.VARIABLE_DEF);
127    }
128
129    /**
130     * Node is on constructor.
131     * @param blockComment DetailAST
132     * @return true if node is before constructor
133     */
134    public static boolean isOnConstructor(DetailAST blockComment) {
135        return isOnPlainToken(blockComment, TokenTypes.CTOR_DEF, TokenTypes.IDENT)
136                || isOnTokenWithModifiers(blockComment, TokenTypes.CTOR_DEF)
137                || isOnTokenWithAnnotation(blockComment, TokenTypes.CTOR_DEF);
138    }
139
140    /**
141     * Node is on enum constant.
142     * @param blockComment DetailAST
143     * @return true if node is before enum constant
144     */
145    public static boolean isOnEnumConstant(DetailAST blockComment) {
146        final boolean isOnPlainConst = blockComment.getParent() != null
147                && blockComment.getParent().getType() == TokenTypes.ENUM_CONSTANT_DEF
148                && getPrevSiblingSkipComments(blockComment).getType() == TokenTypes.ANNOTATIONS
149                && getPrevSiblingSkipComments(blockComment).getChildCount() == 0;
150        final boolean isOnConstWithAnnotation = !isOnPlainConst && blockComment.getParent() != null
151                && blockComment.getParent().getType() == TokenTypes.ANNOTATION
152                && blockComment.getParent().getParent().getParent().getType()
153                    == TokenTypes.ENUM_CONSTANT_DEF;
154        return isOnPlainConst || isOnConstWithAnnotation;
155    }
156
157    /**
158     * Node is on annotation field declaration.
159     * @param blockComment DetailAST
160     * @return true if node is before annotation field
161     */
162    public static boolean isOnAnnotationField(DetailAST blockComment) {
163        return isOnPlainClassMember(blockComment, TokenTypes.ANNOTATION_FIELD_DEF)
164                || isOnTokenWithModifiers(blockComment, TokenTypes.ANNOTATION_FIELD_DEF)
165                || isOnTokenWithAnnotation(blockComment, TokenTypes.ANNOTATION_FIELD_DEF);
166    }
167
168    /**
169     * Checks that block comment is on specified token without any modifiers.
170     * @param blockComment block comment start DetailAST
171     * @param parentTokenType parent token type
172     * @param nextTokenType next token type
173     * @return true if block comment is on specified token without modifiers
174     */
175    private static boolean isOnPlainToken(DetailAST blockComment,
176            int parentTokenType, int nextTokenType) {
177        return blockComment.getParent() != null
178                && blockComment.getParent().getType() == parentTokenType
179                && getPrevSiblingSkipComments(blockComment).getChildCount() == 0
180                && getNextSiblingSkipComments(blockComment).getType() == nextTokenType;
181    }
182
183    /**
184     * Checks that block comment is on specified token with modifiers.
185     * @param blockComment block comment start DetailAST
186     * @param tokenType parent token type
187     * @return true if block comment is on specified token with modifiers
188     */
189    private static boolean isOnTokenWithModifiers(DetailAST blockComment, int tokenType) {
190        return blockComment.getParent() != null
191                && blockComment.getParent().getType() == TokenTypes.MODIFIERS
192                && blockComment.getParent().getParent().getType() == tokenType
193                && getPrevSiblingSkipComments(blockComment) == null;
194    }
195
196    /**
197     * Checks that block comment is on specified token with annotation.
198     * @param blockComment block comment start DetailAST
199     * @param tokenType parent token type
200     * @return true if block comment is on specified token with annotation
201     */
202    private static boolean isOnTokenWithAnnotation(DetailAST blockComment, int tokenType) {
203        return blockComment.getParent() != null
204                && blockComment.getParent().getType() == TokenTypes.ANNOTATION
205                && getPrevSiblingSkipComments(blockComment.getParent()) == null
206                && blockComment.getParent().getParent().getType() == TokenTypes.MODIFIERS
207                && blockComment.getParent().getParent().getParent().getType() == tokenType
208                && getPrevSiblingSkipComments(blockComment) == null;
209    }
210
211    /**
212     * Checks that block comment is on specified class member without any modifiers.
213     * @param blockComment block comment start DetailAST
214     * @param memberType parent token type
215     * @return true if block comment is on specified token without modifiers
216     */
217    private static boolean isOnPlainClassMember(DetailAST blockComment, int memberType) {
218        DetailAST parent = blockComment.getParent();
219        // type could be in fully qualified form, so we go up to Type token
220        while (parent != null && parent.getType() == TokenTypes.DOT) {
221            parent = parent.getParent();
222        }
223        return parent != null
224                && parent.getType() == TokenTypes.TYPE
225                && parent.getParent().getType() == memberType
226                // previous parent sibling is always TokenTypes.MODIFIERS
227                && parent.getPreviousSibling().getChildCount() == 0;
228    }
229
230    /**
231     * Get next sibling node skipping any comment nodes.
232     * @param node current node
233     * @return next sibling
234     */
235    private static DetailAST getNextSiblingSkipComments(DetailAST node) {
236        DetailAST result = node.getNextSibling();
237        while (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
238                || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
239            result = result.getNextSibling();
240        }
241        return result;
242    }
243
244    /**
245     * Get previous sibling node skipping any comments.
246     * @param node current node
247     * @return previous sibling
248     */
249    private static DetailAST getPrevSiblingSkipComments(DetailAST node) {
250        DetailAST result = node.getPreviousSibling();
251        while (result != null
252                && (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
253                || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN)) {
254            result = result.getPreviousSibling();
255        }
256        return result;
257    }
258
259}