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