001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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 class definition.
040     * @param blockComment DetailAST
041     * @return true if node is before class
042     */
043    public static boolean isOnClass(DetailAST blockComment) {
044        return isOnPlainToken(blockComment, TokenTypes.CLASS_DEF, TokenTypes.LITERAL_CLASS)
045                || isOnTokenWithModifiers(blockComment, TokenTypes.CLASS_DEF)
046                || isOnTokenWithAnnotation(blockComment, TokenTypes.CLASS_DEF);
047    }
048
049    /**
050     * Node is on interface definition.
051     * @param blockComment DetailAST
052     * @return true if node is before interface
053     */
054    public static boolean isOnInterface(DetailAST blockComment) {
055        return isOnPlainToken(blockComment, TokenTypes.INTERFACE_DEF, TokenTypes.LITERAL_INTERFACE)
056                || isOnTokenWithModifiers(blockComment, TokenTypes.INTERFACE_DEF)
057                || isOnTokenWithAnnotation(blockComment, TokenTypes.INTERFACE_DEF);
058    }
059
060    /**
061     * Node is on enum definition.
062     * @param blockComment DetailAST
063     * @return true if node is before enum
064     */
065    public static boolean isOnEnum(DetailAST blockComment) {
066        return isOnPlainToken(blockComment, TokenTypes.ENUM_DEF, TokenTypes.ENUM)
067                || isOnTokenWithModifiers(blockComment, TokenTypes.ENUM_DEF)
068                || isOnTokenWithAnnotation(blockComment, TokenTypes.ENUM_DEF);
069    }
070
071    /**
072     * Node is on annotation definition.
073     * @param blockComment DetailAST
074     * @return true if node is before annotation
075     */
076    public static boolean isOnAnnotationDef(DetailAST blockComment) {
077        return isOnPlainToken(blockComment, TokenTypes.ANNOTATION_DEF, TokenTypes.AT)
078                || isOnTokenWithModifiers(blockComment, TokenTypes.ANNOTATION_DEF)
079                || isOnTokenWithAnnotation(blockComment, TokenTypes.ANNOTATION_DEF);
080    }
081
082    /**
083     * Node is on method declaration.
084     * @param blockComment DetailAST
085     * @return true if node is before method
086     */
087    public static boolean isOnMethod(DetailAST blockComment) {
088        return isOnPlainClassMember(blockComment, TokenTypes.METHOD_DEF)
089                || isOnTokenWithModifiers(blockComment, TokenTypes.METHOD_DEF)
090                || isOnTokenWithAnnotation(blockComment, TokenTypes.METHOD_DEF);
091    }
092
093    /**
094     * Node is on field declaration.
095     * @param blockComment DetailAST
096     * @return true if node is before field
097     */
098    public static boolean isOnField(DetailAST blockComment) {
099        return isOnPlainClassMember(blockComment, TokenTypes.VARIABLE_DEF)
100                || isOnTokenWithModifiers(blockComment, TokenTypes.VARIABLE_DEF)
101                || isOnTokenWithAnnotation(blockComment, TokenTypes.VARIABLE_DEF);
102    }
103
104    /**
105     * Node is on constructor.
106     * @param blockComment DetailAST
107     * @return true if node is before constructor
108     */
109    public static boolean isOnConstructor(DetailAST blockComment) {
110        return isOnPlainToken(blockComment, TokenTypes.CTOR_DEF, TokenTypes.IDENT)
111                || isOnTokenWithModifiers(blockComment, TokenTypes.CTOR_DEF)
112                || isOnTokenWithAnnotation(blockComment, TokenTypes.CTOR_DEF);
113    }
114
115    /**
116     * Node is on enum constant.
117     * @param blockComment DetailAST
118     * @return true if node is before enum constant
119     */
120    public static boolean isOnEnumConstant(DetailAST blockComment) {
121        final boolean isOnPlainConst = blockComment.getParent() != null
122                && blockComment.getParent().getType() == TokenTypes.ENUM_CONSTANT_DEF
123                && getPrevSiblingSkipComments(blockComment).getType() == TokenTypes.ANNOTATIONS
124                && getPrevSiblingSkipComments(blockComment).getChildCount() == 0;
125        final boolean isOnConstWithAnnotation = !isOnPlainConst && blockComment.getParent() != null
126                && blockComment.getParent().getType() == TokenTypes.ANNOTATION
127                && blockComment.getParent().getParent().getParent().getType()
128                    == TokenTypes.ENUM_CONSTANT_DEF;
129        return isOnPlainConst || isOnConstWithAnnotation;
130    }
131
132    /**
133     * Checks that block comment is on specified token without any modifiers.
134     * @param blockComment block comment start DetailAST
135     * @param parentTokenType parent token type
136     * @param nextTokenType next token type
137     * @return true if block comment is on specified token without modifiers
138     */
139    private static boolean isOnPlainToken(DetailAST blockComment,
140            int parentTokenType, int nextTokenType) {
141        return blockComment.getParent() != null
142                && blockComment.getParent().getType() == parentTokenType
143                && getPrevSiblingSkipComments(blockComment).getChildCount() == 0
144                && getNextSiblingSkipComments(blockComment).getType() == nextTokenType;
145    }
146
147    /**
148     * Checks that block comment is on specified token with modifiers.
149     * @param blockComment block comment start DetailAST
150     * @param tokenType parent token type
151     * @return true if block comment is on specified token with modifiers
152     */
153    private static boolean isOnTokenWithModifiers(DetailAST blockComment, int tokenType) {
154        return blockComment.getParent() != null
155                && blockComment.getParent().getType() == TokenTypes.MODIFIERS
156                && blockComment.getParent().getParent().getType() == tokenType
157                && getPrevSiblingSkipComments(blockComment) == null;
158    }
159
160    /**
161     * Checks that block comment is on specified token with annotation.
162     * @param blockComment block comment start DetailAST
163     * @param tokenType parent token type
164     * @return true if block comment is on specified token with annotation
165     */
166    private static boolean isOnTokenWithAnnotation(DetailAST blockComment, int tokenType) {
167        return blockComment.getParent() != null
168                && blockComment.getParent().getType() == TokenTypes.ANNOTATION
169                && getPrevSiblingSkipComments(blockComment.getParent()) == null
170                && blockComment.getParent().getParent().getType() == TokenTypes.MODIFIERS
171                && blockComment.getParent().getParent().getParent().getType() == tokenType
172                && getPrevSiblingSkipComments(blockComment) == null;
173    }
174
175    /**
176     * Checks that block comment is on specified class member without any modifiers.
177     * @param blockComment block comment start DetailAST
178     * @param memberType parent token type
179     * @return true if block comment is on specified token without modifiers
180     */
181    private static boolean isOnPlainClassMember(DetailAST blockComment, int memberType) {
182        return blockComment.getParent() != null
183                && blockComment.getParent().getType() == TokenTypes.TYPE
184                && blockComment.getParent().getParent().getType() == memberType
185                // previous parent sibling is always TokenTypes.MODIFIERS
186                && blockComment.getParent().getPreviousSibling().getChildCount() == 0;
187    }
188
189    /**
190     * Get next sibling node skipping any comment nodes.
191     * @param node current node
192     * @return next sibling
193     */
194    private static DetailAST getNextSiblingSkipComments(DetailAST node) {
195        DetailAST result = node.getNextSibling();
196        while (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
197                || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
198            result = result.getNextSibling();
199        }
200        return result;
201    }
202
203    /**
204     * Get previous sibling node skipping any comments.
205     * @param node current node
206     * @return previous sibling
207     */
208    private static DetailAST getPrevSiblingSkipComments(DetailAST node) {
209        DetailAST result = node.getPreviousSibling();
210        while (result != null
211                && (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
212                || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN)) {
213            result = result.getPreviousSibling();
214        }
215        return result;
216    }
217}