001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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.checks.whitespace; 021 022import java.util.Arrays; 023 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 027 028/** 029 * <p>Checks the padding of parentheses; that is whether a space is required 030 * after a left parenthesis and before a right parenthesis, or such spaces are 031 * forbidden. No check occurs at the right parenthesis after an empty for 032 * iterator, at the left parenthesis before an empty for initialization, or at 033 * the right parenthesis of a try-with-resources resource specification where 034 * the last resource variable has a trailing semi-colon. 035 * Use Check {@link EmptyForIteratorPadCheck EmptyForIteratorPad} to validate 036 * empty for iterators and {@link EmptyForInitializerPadCheck EmptyForInitializerPad} 037 * to validate empty for initializers. Typecasts are also not checked, as there is 038 * {@link TypecastParenPadCheck TypecastParenPad} to validate them. 039 * </p> 040 * <p> 041 * The policy to verify is specified using the {@link PadOption} class and 042 * defaults to {@link PadOption#NOSPACE}. 043 * </p> 044 * <p> By default the check will check parentheses that occur with the following 045 * tokens: 046 * {@link TokenTypes#ANNOTATION ANNOTATION}, 047 * {@link TokenTypes#ANNOTATION_FIELD_DEF ANNOTATION_FIELD_DEF}, 048 * {@link TokenTypes#CTOR_DEF CTOR_DEF}, 049 * {@link TokenTypes#CTOR_CALL CTOR_CALL}, 050 * {@link TokenTypes#DOT DOT}, 051 * {@link TokenTypes#ENUM_CONSTANT_DEF ENUM_CONSTANT_DEF}, 052 * {@link TokenTypes#EXPR EXPR}, 053 * {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}, 054 * {@link TokenTypes#LITERAL_DO LITERAL_DO}, 055 * {@link TokenTypes#LITERAL_FOR LITERAL_FOR}, 056 * {@link TokenTypes#LITERAL_IF LITERAL_IF}, 057 * {@link TokenTypes#LITERAL_NEW LITERAL_NEW}, 058 * {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH}, 059 * {@link TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED}, 060 * {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}, 061 * {@link TokenTypes#METHOD_CALL METHOD_CALL}, 062 * {@link TokenTypes#METHOD_DEF METHOD_DEF}, 063 * {@link TokenTypes#RESOURCE_SPECIFICATION RESOURCE_SPECIFICATION}, 064 * {@link TokenTypes#SUPER_CTOR_CALL SUPER_CTOR_CALL}, 065 * {@link TokenTypes#QUESTION QUESTION}, 066 * {@link TokenTypes#LAMBDA LAMBDA}, 067 * </p> 068 * <p> 069 * An example of how to configure the check is: 070 * </p> 071 * <pre> 072 * <module name="ParenPad"/> 073 * </pre> 074 * <p> 075 * An example of how to configure the check to require spaces for the 076 * parentheses of constructor, method, and super constructor invocations is: 077 * </p> 078 * <pre> 079 * <module name="ParenPad"> 080 * <property name="tokens" 081 * value="CTOR_CALL, METHOD_CALL, SUPER_CTOR_CALL"/> 082 * <property name="option" value="space"/> 083 * </module> 084 * </pre> 085 */ 086public class ParenPadCheck extends AbstractParenPadCheck { 087 088 /** 089 * The array of Acceptable Tokens. 090 */ 091 private final int[] acceptableTokens; 092 093 /** 094 * Initializes and sorts acceptableTokens to make binary search over it possible. 095 */ 096 public ParenPadCheck() { 097 acceptableTokens = makeAcceptableTokens(); 098 Arrays.sort(acceptableTokens); 099 } 100 101 @Override 102 public int[] getDefaultTokens() { 103 return makeAcceptableTokens(); 104 } 105 106 @Override 107 public int[] getAcceptableTokens() { 108 return makeAcceptableTokens(); 109 } 110 111 @Override 112 public int[] getRequiredTokens() { 113 return CommonUtil.EMPTY_INT_ARRAY; 114 } 115 116 @Override 117 public void visitToken(DetailAST ast) { 118 switch (ast.getType()) { 119 case TokenTypes.METHOD_CALL: 120 processLeft(ast); 121 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 122 break; 123 case TokenTypes.DOT: 124 case TokenTypes.EXPR: 125 case TokenTypes.QUESTION: 126 processExpression(ast); 127 break; 128 case TokenTypes.LITERAL_FOR: 129 visitLiteralFor(ast); 130 break; 131 case TokenTypes.ANNOTATION: 132 case TokenTypes.ENUM_CONSTANT_DEF: 133 case TokenTypes.LITERAL_NEW: 134 case TokenTypes.LITERAL_SYNCHRONIZED: 135 case TokenTypes.LAMBDA: 136 visitTokenWithOptionalParentheses(ast); 137 break; 138 case TokenTypes.RESOURCE_SPECIFICATION: 139 visitResourceSpecification(ast); 140 break; 141 default: 142 processLeft(ast.findFirstToken(TokenTypes.LPAREN)); 143 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 144 } 145 } 146 147 /** 148 * Checks parens in token which may not contain parens, e.g. 149 * {@link TokenTypes#ENUM_CONSTANT_DEF}, {@link TokenTypes#ANNOTATION} 150 * {@link TokenTypes#LITERAL_SYNCHRONIZED}, {@link TokenTypes#LITERAL_NEW} and 151 * {@link TokenTypes#LAMBDA}. 152 * @param ast the token to check. 153 */ 154 private void visitTokenWithOptionalParentheses(DetailAST ast) { 155 final DetailAST parenAst = ast.findFirstToken(TokenTypes.LPAREN); 156 if (parenAst != null) { 157 processLeft(parenAst); 158 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 159 } 160 } 161 162 /** 163 * Checks parens in {@link TokenTypes#RESOURCE_SPECIFICATION}. 164 * @param ast the token to check. 165 */ 166 private void visitResourceSpecification(DetailAST ast) { 167 processLeft(ast.findFirstToken(TokenTypes.LPAREN)); 168 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 169 if (!hasPrecedingSemiColon(rparen)) { 170 processRight(rparen); 171 } 172 } 173 174 /** 175 * Checks that a token is preceded by a semi-colon. 176 * @param ast the token to check 177 * @return whether a token is preceded by a semi-colon 178 */ 179 private static boolean hasPrecedingSemiColon(DetailAST ast) { 180 return ast.getPreviousSibling().getType() == TokenTypes.SEMI; 181 } 182 183 /** 184 * Checks parens in {@link TokenTypes#LITERAL_FOR}. 185 * @param ast the token to check. 186 */ 187 private void visitLiteralFor(DetailAST ast) { 188 final DetailAST lparen = ast.findFirstToken(TokenTypes.LPAREN); 189 if (!isPrecedingEmptyForInit(lparen)) { 190 processLeft(lparen); 191 } 192 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 193 if (!isFollowsEmptyForIterator(rparen)) { 194 processRight(rparen); 195 } 196 } 197 198 /** 199 * Checks parens inside {@link TokenTypes#EXPR}, {@link TokenTypes#QUESTION} 200 * and {@link TokenTypes#METHOD_CALL}. 201 * @param ast the token to check. 202 */ 203 private void processExpression(DetailAST ast) { 204 DetailAST childAst = ast.getFirstChild(); 205 while (childAst != null) { 206 if (childAst.getType() == TokenTypes.LPAREN) { 207 processLeft(childAst); 208 } 209 else if (childAst.getType() == TokenTypes.RPAREN && !isInTypecast(childAst)) { 210 processRight(childAst); 211 } 212 else if (!isAcceptableToken(childAst)) { 213 //Traverse all subtree tokens which will never be configured 214 //to be launched in visitToken() 215 processExpression(childAst); 216 } 217 childAst = childAst.getNextSibling(); 218 } 219 } 220 221 /** 222 * Checks whether AcceptableTokens contains the given ast. 223 * @param ast the token to check. 224 * @return true if the ast is in AcceptableTokens. 225 */ 226 private boolean isAcceptableToken(DetailAST ast) { 227 boolean result = false; 228 if (Arrays.binarySearch(acceptableTokens, ast.getType()) >= 0) { 229 result = true; 230 } 231 return result; 232 } 233 234 /** 235 * Returns array of acceptable tokens. 236 * @return acceptableTokens. 237 */ 238 private static int[] makeAcceptableTokens() { 239 return new int[] {TokenTypes.ANNOTATION, 240 TokenTypes.ANNOTATION_FIELD_DEF, 241 TokenTypes.CTOR_CALL, 242 TokenTypes.CTOR_DEF, 243 TokenTypes.DOT, 244 TokenTypes.ENUM_CONSTANT_DEF, 245 TokenTypes.EXPR, 246 TokenTypes.LITERAL_CATCH, 247 TokenTypes.LITERAL_DO, 248 TokenTypes.LITERAL_FOR, 249 TokenTypes.LITERAL_IF, 250 TokenTypes.LITERAL_NEW, 251 TokenTypes.LITERAL_SWITCH, 252 TokenTypes.LITERAL_SYNCHRONIZED, 253 TokenTypes.LITERAL_WHILE, 254 TokenTypes.METHOD_CALL, 255 TokenTypes.METHOD_DEF, 256 TokenTypes.QUESTION, 257 TokenTypes.RESOURCE_SPECIFICATION, 258 TokenTypes.SUPER_CTOR_CALL, 259 TokenTypes.LAMBDA, 260 }; 261 } 262 263 /** 264 * Checks whether {@link TokenTypes#RPAREN} is a closing paren 265 * of a {@link TokenTypes#TYPECAST}. 266 * @param ast of a {@link TokenTypes#RPAREN} to check. 267 * @return true if ast is a closing paren of a {@link TokenTypes#TYPECAST}. 268 */ 269 private static boolean isInTypecast(DetailAST ast) { 270 boolean result = false; 271 if (ast.getParent().getType() == TokenTypes.TYPECAST) { 272 final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN); 273 if (firstRparen.getLineNo() == ast.getLineNo() 274 && firstRparen.getColumnNo() == ast.getColumnNo()) { 275 result = true; 276 } 277 } 278 return result; 279 } 280 281 /** 282 * Checks that a token follows an empty for iterator. 283 * @param ast the token to check 284 * @return whether a token follows an empty for iterator 285 */ 286 private static boolean isFollowsEmptyForIterator(DetailAST ast) { 287 boolean result = false; 288 final DetailAST parent = ast.getParent(); 289 //Only traditional for statements are examined, not for-each statements 290 if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { 291 final DetailAST forIterator = 292 parent.findFirstToken(TokenTypes.FOR_ITERATOR); 293 result = forIterator.getChildCount() == 0; 294 } 295 return result; 296 } 297 298 /** 299 * Checks that a token precedes an empty for initializer. 300 * @param ast the token to check 301 * @return whether a token precedes an empty for initializer 302 */ 303 private static boolean isPrecedingEmptyForInit(DetailAST ast) { 304 boolean result = false; 305 final DetailAST parent = ast.getParent(); 306 //Only traditional for statements are examined, not for-each statements 307 if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { 308 final DetailAST forIterator = 309 parent.findFirstToken(TokenTypes.FOR_INIT); 310 result = forIterator.getChildCount() == 0; 311 } 312 return result; 313 } 314 315}