1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.utils;
21  
22  import com.puppycrawl.tools.checkstyle.api.DetailAST;
23  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
24  
25  /**
26   * Utility class that has methods to check javadoc comment position in java file.
27   *
28   */
29  public final class BlockCommentPosition {
30  
31      /**
32       * Forbid new instances.
33       */
34      private BlockCommentPosition() {
35      }
36  
37      /**
38       * Node is on type definition.
39       * @param blockComment DetailAST
40       * @return true if node is before class, interface, enum or annotation.
41       */
42      public static boolean isOnType(DetailAST blockComment) {
43          return isOnClass(blockComment)
44                  || isOnInterface(blockComment)
45                  || isOnEnum(blockComment)
46                  || isOnAnnotationDef(blockComment);
47      }
48  
49      /**
50       * Node is on class definition.
51       * @param blockComment DetailAST
52       * @return true if node is before class
53       */
54      public static boolean isOnClass(DetailAST blockComment) {
55          return isOnPlainToken(blockComment, TokenTypes.CLASS_DEF, TokenTypes.LITERAL_CLASS)
56                  || isOnTokenWithModifiers(blockComment, TokenTypes.CLASS_DEF)
57                  || isOnTokenWithAnnotation(blockComment, TokenTypes.CLASS_DEF);
58      }
59  
60      /**
61       * Node is on package definition.
62       * @param blockComment DetailAST
63       * @return true if node is before package
64       */
65      public static boolean isOnPackage(DetailAST blockComment) {
66          final DetailAST nextSibling = blockComment.getNextSibling();
67          return isOnTokenWithAnnotation(blockComment, TokenTypes.PACKAGE_DEF)
68                  || nextSibling != null && nextSibling.getType() == TokenTypes.PACKAGE_DEF;
69      }
70  
71      /**
72       * Node is on interface definition.
73       * @param blockComment DetailAST
74       * @return true if node is before interface
75       */
76      public static boolean isOnInterface(DetailAST blockComment) {
77          return isOnPlainToken(blockComment, TokenTypes.INTERFACE_DEF, TokenTypes.LITERAL_INTERFACE)
78                  || isOnTokenWithModifiers(blockComment, TokenTypes.INTERFACE_DEF)
79                  || isOnTokenWithAnnotation(blockComment, TokenTypes.INTERFACE_DEF);
80      }
81  
82      /**
83       * Node is on enum definition.
84       * @param blockComment DetailAST
85       * @return true if node is before enum
86       */
87      public static boolean isOnEnum(DetailAST blockComment) {
88          return isOnPlainToken(blockComment, TokenTypes.ENUM_DEF, TokenTypes.ENUM)
89                  || isOnTokenWithModifiers(blockComment, TokenTypes.ENUM_DEF)
90                  || isOnTokenWithAnnotation(blockComment, TokenTypes.ENUM_DEF);
91      }
92  
93      /**
94       * Node is on annotation definition.
95       * @param blockComment DetailAST
96       * @return true if node is before annotation
97       */
98      public static boolean isOnAnnotationDef(DetailAST blockComment) {
99          return isOnPlainToken(blockComment, TokenTypes.ANNOTATION_DEF, TokenTypes.AT)
100                 || isOnTokenWithModifiers(blockComment, TokenTypes.ANNOTATION_DEF)
101                 || isOnTokenWithAnnotation(blockComment, TokenTypes.ANNOTATION_DEF);
102     }
103 
104     /**
105      * Node is on type member declaration.
106      * @param blockComment DetailAST
107      * @return true if node is before method, field, constructor, enum constant
108      *     or annotation field
109      */
110     public static boolean isOnMember(DetailAST blockComment) {
111         return isOnMethod(blockComment)
112                 || isOnField(blockComment)
113                 || isOnConstructor(blockComment)
114                 || isOnEnumConstant(blockComment)
115                 || isOnAnnotationField(blockComment);
116     }
117 
118     /**
119      * Node is on method declaration.
120      * @param blockComment DetailAST
121      * @return true if node is before method
122      */
123     public static boolean isOnMethod(DetailAST blockComment) {
124         return isOnPlainClassMember(blockComment, TokenTypes.METHOD_DEF)
125                 || isOnTokenWithModifiers(blockComment, TokenTypes.METHOD_DEF)
126                 || isOnTokenWithAnnotation(blockComment, TokenTypes.METHOD_DEF);
127     }
128 
129     /**
130      * Node is on field declaration.
131      * @param blockComment DetailAST
132      * @return true if node is before field
133      */
134     public static boolean isOnField(DetailAST blockComment) {
135         return isOnPlainClassMember(blockComment, TokenTypes.VARIABLE_DEF)
136                 || isOnTokenWithModifiers(blockComment, TokenTypes.VARIABLE_DEF)
137                 || isOnTokenWithAnnotation(blockComment, TokenTypes.VARIABLE_DEF);
138     }
139 
140     /**
141      * Node is on constructor.
142      * @param blockComment DetailAST
143      * @return true if node is before constructor
144      */
145     public static boolean isOnConstructor(DetailAST blockComment) {
146         return isOnPlainToken(blockComment, TokenTypes.CTOR_DEF, TokenTypes.IDENT)
147                 || isOnTokenWithModifiers(blockComment, TokenTypes.CTOR_DEF)
148                 || isOnTokenWithAnnotation(blockComment, TokenTypes.CTOR_DEF);
149     }
150 
151     /**
152      * Node is on enum constant.
153      * @param blockComment DetailAST
154      * @return true if node is before enum constant
155      */
156     public static boolean isOnEnumConstant(DetailAST blockComment) {
157         final boolean isOnPlainConst = blockComment.getParent() != null
158                 && blockComment.getParent().getType() == TokenTypes.ENUM_CONSTANT_DEF
159                 && getPrevSiblingSkipComments(blockComment).getType() == TokenTypes.ANNOTATIONS
160                 && getPrevSiblingSkipComments(blockComment).getChildCount() == 0;
161         final boolean isOnConstWithAnnotation = !isOnPlainConst && blockComment.getParent() != null
162                 && blockComment.getParent().getType() == TokenTypes.ANNOTATION
163                 && blockComment.getParent().getParent().getParent().getType()
164                     == TokenTypes.ENUM_CONSTANT_DEF;
165         return isOnPlainConst || isOnConstWithAnnotation;
166     }
167 
168     /**
169      * Node is on annotation field declaration.
170      * @param blockComment DetailAST
171      * @return true if node is before annotation field
172      */
173     public static boolean isOnAnnotationField(DetailAST blockComment) {
174         return isOnPlainClassMember(blockComment, TokenTypes.ANNOTATION_FIELD_DEF)
175                 || isOnTokenWithModifiers(blockComment, TokenTypes.ANNOTATION_FIELD_DEF)
176                 || isOnTokenWithAnnotation(blockComment, TokenTypes.ANNOTATION_FIELD_DEF);
177     }
178 
179     /**
180      * Checks that block comment is on specified token without any modifiers.
181      * @param blockComment block comment start DetailAST
182      * @param parentTokenType parent token type
183      * @param nextTokenType next token type
184      * @return true if block comment is on specified token without modifiers
185      */
186     private static boolean isOnPlainToken(DetailAST blockComment,
187             int parentTokenType, int nextTokenType) {
188         return blockComment.getParent() != null
189                 && blockComment.getParent().getType() == parentTokenType
190                 && getPrevSiblingSkipComments(blockComment).getChildCount() == 0
191                 && getNextSiblingSkipComments(blockComment).getType() == nextTokenType;
192     }
193 
194     /**
195      * Checks that block comment is on specified token with modifiers.
196      * @param blockComment block comment start DetailAST
197      * @param tokenType parent token type
198      * @return true if block comment is on specified token with modifiers
199      */
200     private static boolean isOnTokenWithModifiers(DetailAST blockComment, int tokenType) {
201         return blockComment.getParent() != null
202                 && blockComment.getParent().getType() == TokenTypes.MODIFIERS
203                 && blockComment.getParent().getParent().getType() == tokenType
204                 && getPrevSiblingSkipComments(blockComment) == null;
205     }
206 
207     /**
208      * Checks that block comment is on specified token with annotation.
209      * @param blockComment block comment start DetailAST
210      * @param tokenType parent token type
211      * @return true if block comment is on specified token with annotation
212      */
213     private static boolean isOnTokenWithAnnotation(DetailAST blockComment, int tokenType) {
214         return blockComment.getParent() != null
215                 && blockComment.getParent().getType() == TokenTypes.ANNOTATION
216                 && getPrevSiblingSkipComments(blockComment.getParent()) == null
217                 && blockComment.getParent().getParent().getParent().getType() == tokenType
218                 && getPrevSiblingSkipComments(blockComment) == null;
219     }
220 
221     /**
222      * Checks that block comment is on specified class member without any modifiers.
223      * @param blockComment block comment start DetailAST
224      * @param memberType parent token type
225      * @return true if block comment is on specified token without modifiers
226      */
227     private static boolean isOnPlainClassMember(DetailAST blockComment, int memberType) {
228         DetailAST parent = blockComment.getParent();
229         // type could be in fully qualified form, so we go up to Type token
230         while (parent != null && (parent.getType() == TokenTypes.DOT
231                 || parent.getType() == TokenTypes.ARRAY_DECLARATOR)) {
232             parent = parent.getParent();
233         }
234         return parent != null
235                 && (parent.getType() == TokenTypes.TYPE
236                     || parent.getType() == TokenTypes.TYPE_PARAMETERS)
237                 && parent.getParent().getType() == memberType
238                 // previous parent sibling is always TokenTypes.MODIFIERS
239                 && parent.getPreviousSibling().getChildCount() == 0;
240     }
241 
242     /**
243      * Get next sibling node skipping any comment nodes.
244      * @param node current node
245      * @return next sibling
246      */
247     private static DetailAST getNextSiblingSkipComments(DetailAST node) {
248         DetailAST result = node.getNextSibling();
249         while (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
250                 || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
251             result = result.getNextSibling();
252         }
253         return result;
254     }
255 
256     /**
257      * Get previous sibling node skipping any comments.
258      * @param node current node
259      * @return previous sibling
260      */
261     private static DetailAST getPrevSiblingSkipComments(DetailAST node) {
262         DetailAST result = node.getPreviousSibling();
263         while (result != null
264                 && (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
265                 || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN)) {
266             result = result.getPreviousSibling();
267         }
268         return result;
269     }
270 
271 }