View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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.checks.whitespace;
21  
22  import java.util.Arrays;
23  
24  import com.puppycrawl.tools.checkstyle.api.DetailAST;
25  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
26  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
27  
28  /**
29   * <p>Checks the padding of parentheses; that is whether a space is required
30   * after a left parenthesis and before a right parenthesis, or such spaces are
31   * forbidden. No check occurs at the right parenthesis after an empty for
32   * iterator, at the left parenthesis before an empty for initialization, or at
33   * the right parenthesis of a try-with-resources resource specification where
34   * the last resource variable has a trailing semi-colon.
35   * Use Check {@link EmptyForIteratorPadCheck EmptyForIteratorPad} to validate
36   * empty for iterators and {@link EmptyForInitializerPadCheck EmptyForInitializerPad}
37   * to validate empty for initializers. Typecasts are also not checked, as there is
38   * {@link TypecastParenPadCheck TypecastParenPad} to validate them.
39   * </p>
40   * <p>
41   * The policy to verify is specified using the {@link PadOption} class and
42   * defaults to {@link PadOption#NOSPACE}.
43   * </p>
44   * <p> By default the check will check parentheses that occur with the following
45   * tokens:
46   *  {@link TokenTypes#ANNOTATION ANNOTATION},
47   *  {@link TokenTypes#ANNOTATION_FIELD_DEF ANNOTATION_FIELD_DEF},
48   *  {@link TokenTypes#CTOR_DEF CTOR_DEF},
49   *  {@link TokenTypes#CTOR_CALL CTOR_CALL},
50   *  {@link TokenTypes#DOT DOT},
51   *  {@link TokenTypes#ENUM_CONSTANT_DEF ENUM_CONSTANT_DEF},
52   *  {@link TokenTypes#EXPR EXPR},
53   *  {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH},
54   *  {@link TokenTypes#LITERAL_DO LITERAL_DO},
55   *  {@link TokenTypes#LITERAL_FOR LITERAL_FOR},
56   *  {@link TokenTypes#LITERAL_IF LITERAL_IF},
57   *  {@link TokenTypes#LITERAL_NEW LITERAL_NEW},
58   *  {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH},
59   *  {@link TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED},
60   *  {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE},
61   *  {@link TokenTypes#METHOD_CALL METHOD_CALL},
62   *  {@link TokenTypes#METHOD_DEF METHOD_DEF},
63   *  {@link TokenTypes#RESOURCE_SPECIFICATION RESOURCE_SPECIFICATION},
64   *  {@link TokenTypes#SUPER_CTOR_CALL SUPER_CTOR_CALL},
65   *  {@link TokenTypes#QUESTION QUESTION},
66   *  {@link TokenTypes#LAMBDA LAMBDA},
67   * </p>
68   * <p>
69   * An example of how to configure the check is:
70   * </p>
71   * <pre>
72   * &lt;module name="ParenPad"/&gt;
73   * </pre>
74   * <p>
75   * An example of how to configure the check to require spaces for the
76   * parentheses of constructor, method, and super constructor invocations is:
77   * </p>
78   * <pre>
79   * &lt;module name="ParenPad"&gt;
80   *     &lt;property name="tokens"
81   *               value="CTOR_CALL, METHOD_CALL, SUPER_CTOR_CALL"/&gt;
82   *     &lt;property name="option" value="space"/&gt;
83   * &lt;/module&gt;
84   * </pre>
85   * @author Oliver Burn
86   * @author Vladislav Lisetskiy
87   */
88  public class ParenPadCheck extends AbstractParenPadCheck {
89  
90      /**
91       * The array of Acceptable Tokens.
92       */
93      private final int[] acceptableTokens;
94  
95      /**
96       * Initializes and sorts acceptableTokens to make binary search over it possible.
97       */
98      public ParenPadCheck() {
99          acceptableTokens = makeAcceptableTokens();
100         Arrays.sort(acceptableTokens);
101     }
102 
103     @Override
104     public int[] getDefaultTokens() {
105         return makeAcceptableTokens();
106     }
107 
108     @Override
109     public int[] getAcceptableTokens() {
110         return makeAcceptableTokens();
111     }
112 
113     @Override
114     public int[] getRequiredTokens() {
115         return CommonUtils.EMPTY_INT_ARRAY;
116     }
117 
118     @Override
119     public void visitToken(DetailAST ast) {
120         switch (ast.getType()) {
121             case TokenTypes.METHOD_CALL:
122                 processLeft(ast);
123                 processRight(ast.findFirstToken(TokenTypes.RPAREN));
124                 break;
125             case TokenTypes.DOT:
126             case TokenTypes.EXPR:
127             case TokenTypes.QUESTION:
128                 processExpression(ast);
129                 break;
130             case TokenTypes.LITERAL_FOR:
131                 visitLiteralFor(ast);
132                 break;
133             case TokenTypes.ANNOTATION:
134             case TokenTypes.ENUM_CONSTANT_DEF:
135             case TokenTypes.LITERAL_NEW:
136             case TokenTypes.LITERAL_SYNCHRONIZED:
137             case TokenTypes.LAMBDA:
138                 visitTokenWithOptionalParentheses(ast);
139                 break;
140             case TokenTypes.RESOURCE_SPECIFICATION:
141                 visitResourceSpecification(ast);
142                 break;
143             default:
144                 processLeft(ast.findFirstToken(TokenTypes.LPAREN));
145                 processRight(ast.findFirstToken(TokenTypes.RPAREN));
146         }
147     }
148 
149     /**
150      * Checks parens in token which may not contain parens, e.g.
151      * {@link TokenTypes#ENUM_CONSTANT_DEF}, {@link TokenTypes#ANNOTATION}
152      * {@link TokenTypes#LITERAL_SYNCHRONIZED}, {@link TokenTypes#LITERAL_NEW} and
153      * {@link TokenTypes#LAMBDA}.
154      * @param ast the token to check.
155      */
156     private void visitTokenWithOptionalParentheses(DetailAST ast) {
157         final DetailAST parenAst = ast.findFirstToken(TokenTypes.LPAREN);
158         if (parenAst != null) {
159             processLeft(parenAst);
160             processRight(ast.findFirstToken(TokenTypes.RPAREN));
161         }
162     }
163 
164     /**
165      * Checks parens in {@link TokenTypes#RESOURCE_SPECIFICATION}.
166      * @param ast the token to check.
167      */
168     private void visitResourceSpecification(DetailAST ast) {
169         processLeft(ast.findFirstToken(TokenTypes.LPAREN));
170         final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
171         if (!hasPrecedingSemiColon(rparen)) {
172             processRight(rparen);
173         }
174     }
175 
176     /**
177      * Checks that a token is preceded by a semi-colon.
178      * @param ast the token to check
179      * @return whether a token is preceded by a semi-colon
180      */
181     private static boolean hasPrecedingSemiColon(DetailAST ast) {
182         return ast.getPreviousSibling().getType() == TokenTypes.SEMI;
183     }
184 
185     /**
186      * Checks parens in {@link TokenTypes#LITERAL_FOR}.
187      * @param ast the token to check.
188      */
189     private void visitLiteralFor(DetailAST ast) {
190         final DetailAST lparen = ast.findFirstToken(TokenTypes.LPAREN);
191         if (!isPrecedingEmptyForInit(lparen)) {
192             processLeft(lparen);
193         }
194         final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
195         if (!isFollowsEmptyForIterator(rparen)) {
196             processRight(rparen);
197         }
198     }
199 
200     /**
201      * Checks parens inside {@link TokenTypes#EXPR}, {@link TokenTypes#QUESTION}
202      * and {@link TokenTypes#METHOD_CALL}.
203      * @param ast the token to check.
204      */
205     private void processExpression(DetailAST ast) {
206         if (ast.branchContains(TokenTypes.LPAREN)) {
207             DetailAST childAst = ast.getFirstChild();
208             while (childAst != null) {
209                 if (childAst.getType() == TokenTypes.LPAREN) {
210                     processLeft(childAst);
211                 }
212                 else if (childAst.getType() == TokenTypes.RPAREN && !isInTypecast(childAst)) {
213                     processRight(childAst);
214                 }
215                 else if (!isAcceptableToken(childAst)) {
216                     //Traverse all subtree tokens which will never be configured
217                     //to be launched in visitToken()
218                     processExpression(childAst);
219                 }
220                 childAst = childAst.getNextSibling();
221             }
222         }
223     }
224 
225     /**
226      * Checks whether AcceptableTokens contains the given ast.
227      * @param ast the token to check.
228      * @return true if the ast is in AcceptableTokens.
229      */
230     private boolean isAcceptableToken(DetailAST ast) {
231         boolean result = false;
232         if (Arrays.binarySearch(acceptableTokens, ast.getType()) >= 0) {
233             result = true;
234         }
235         return result;
236     }
237 
238     /**
239      * Returns array of acceptable tokens.
240      * @return acceptableTokens.
241      */
242     private static int[] makeAcceptableTokens() {
243         return new int[] {TokenTypes.ANNOTATION,
244             TokenTypes.ANNOTATION_FIELD_DEF,
245             TokenTypes.CTOR_CALL,
246             TokenTypes.CTOR_DEF,
247             TokenTypes.DOT,
248             TokenTypes.ENUM_CONSTANT_DEF,
249             TokenTypes.EXPR,
250             TokenTypes.LITERAL_CATCH,
251             TokenTypes.LITERAL_DO,
252             TokenTypes.LITERAL_FOR,
253             TokenTypes.LITERAL_IF,
254             TokenTypes.LITERAL_NEW,
255             TokenTypes.LITERAL_SWITCH,
256             TokenTypes.LITERAL_SYNCHRONIZED,
257             TokenTypes.LITERAL_WHILE,
258             TokenTypes.METHOD_CALL,
259             TokenTypes.METHOD_DEF,
260             TokenTypes.QUESTION,
261             TokenTypes.RESOURCE_SPECIFICATION,
262             TokenTypes.SUPER_CTOR_CALL,
263             TokenTypes.LAMBDA,
264         };
265     }
266 
267     /**
268      * Checks whether {@link TokenTypes#RPAREN} is a closing paren
269      * of a {@link TokenTypes#TYPECAST}.
270      * @param ast of a {@link TokenTypes#RPAREN} to check.
271      * @return true if ast is a closing paren of a {@link TokenTypes#TYPECAST}.
272      */
273     private static boolean isInTypecast(DetailAST ast) {
274         boolean result = false;
275         if (ast.getParent().getType() == TokenTypes.TYPECAST) {
276             final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN);
277             if (firstRparen.getLineNo() == ast.getLineNo()
278                     && firstRparen.getColumnNo() == ast.getColumnNo()) {
279                 result = true;
280             }
281         }
282         return result;
283     }
284 
285     /**
286      * Checks that a token follows an empty for iterator.
287      * @param ast the token to check
288      * @return whether a token follows an empty for iterator
289      */
290     private static boolean isFollowsEmptyForIterator(DetailAST ast) {
291         boolean result = false;
292         final DetailAST parent = ast.getParent();
293         //Only traditional for statements are examined, not for-each statements
294         if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
295             final DetailAST forIterator =
296                 parent.findFirstToken(TokenTypes.FOR_ITERATOR);
297             result = forIterator.getChildCount() == 0;
298         }
299         return result;
300     }
301 
302     /**
303      * Checks that a token precedes an empty for initializer.
304      * @param ast the token to check
305      * @return whether a token precedes an empty for initializer
306      */
307     private static boolean isPrecedingEmptyForInit(DetailAST ast) {
308         boolean result = false;
309         final DetailAST parent = ast.getParent();
310         //Only traditional for statements are examined, not for-each statements
311         if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) {
312             final DetailAST forIterator =
313                     parent.findFirstToken(TokenTypes.FOR_INIT);
314             result = forIterator.getChildCount() == 0;
315         }
316         return result;
317     }
318 }