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.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.CommonUtils; 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 * @author Oliver Burn 086 * @author Vladislav Lisetskiy 087 */ 088public class ParenPadCheck extends AbstractParenPadCheck { 089 090 /** 091 * The array of Acceptable Tokens. 092 */ 093 private final int[] acceptableTokens; 094 095 /** 096 * Initializes and sorts acceptableTokens to make binary search over it possible. 097 */ 098 public ParenPadCheck() { 099 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 processExpression(ast); 125 break; 126 case TokenTypes.DOT: 127 case TokenTypes.EXPR: 128 case TokenTypes.QUESTION: 129 processExpression(ast); 130 break; 131 case TokenTypes.LITERAL_FOR: 132 visitLiteralFor(ast); 133 break; 134 case TokenTypes.ANNOTATION: 135 case TokenTypes.ENUM_CONSTANT_DEF: 136 case TokenTypes.LITERAL_NEW: 137 case TokenTypes.LITERAL_SYNCHRONIZED: 138 case TokenTypes.LAMBDA: 139 visitTokenWithOptionalParentheses(ast); 140 break; 141 case TokenTypes.RESOURCE_SPECIFICATION: 142 visitResourceSpecification(ast); 143 break; 144 default: 145 processLeft(ast.findFirstToken(TokenTypes.LPAREN)); 146 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 147 } 148 } 149 150 /** 151 * Checks parens in token which may not contain parens, e.g. 152 * {@link TokenTypes#ENUM_CONSTANT_DEF}, {@link TokenTypes#ANNOTATION} 153 * {@link TokenTypes#LITERAL_SYNCHRONIZED}, {@link TokenTypes#LITERAL_NEW} and 154 * {@link TokenTypes#LAMBDA}. 155 * @param ast the token to check. 156 */ 157 private void visitTokenWithOptionalParentheses(DetailAST ast) { 158 final DetailAST parenAst = ast.findFirstToken(TokenTypes.LPAREN); 159 if (parenAst != null) { 160 processLeft(parenAst); 161 processRight(ast.findFirstToken(TokenTypes.RPAREN)); 162 } 163 } 164 165 /** 166 * Checks parens in {@link TokenTypes#RESOURCE_SPECIFICATION}. 167 * @param ast the token to check. 168 */ 169 private void visitResourceSpecification(DetailAST ast) { 170 processLeft(ast.findFirstToken(TokenTypes.LPAREN)); 171 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 172 if (!hasPrecedingSemiColon(rparen)) { 173 processRight(rparen); 174 } 175 } 176 177 /** 178 * Checks that a token is preceded by a semi-colon. 179 * @param ast the token to check 180 * @return whether a token is preceded by a semi-colon 181 */ 182 private static boolean hasPrecedingSemiColon(DetailAST ast) { 183 return ast.getPreviousSibling().getType() == TokenTypes.SEMI; 184 } 185 186 /** 187 * Checks parens in {@link TokenTypes#LITERAL_FOR}. 188 * @param ast the token to check. 189 */ 190 private void visitLiteralFor(DetailAST ast) { 191 final DetailAST lparen = ast.findFirstToken(TokenTypes.LPAREN); 192 if (!isPrecedingEmptyForInit(lparen)) { 193 processLeft(lparen); 194 } 195 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); 196 if (!isFollowsEmptyForIterator(rparen)) { 197 processRight(rparen); 198 } 199 } 200 201 /** 202 * Checks parens inside {@link TokenTypes#EXPR}, {@link TokenTypes#QUESTION} 203 * and {@link TokenTypes#METHOD_CALL}. 204 * @param ast the token to check. 205 */ 206 private void processExpression(DetailAST ast) { 207 if (ast.branchContains(TokenTypes.LPAREN)) { 208 DetailAST childAst = ast.getFirstChild(); 209 while (childAst != null) { 210 if (childAst.getType() == TokenTypes.LPAREN) { 211 processLeft(childAst); 212 processExpression(childAst); 213 } 214 else if (childAst.getType() == TokenTypes.RPAREN && !isInTypecast(childAst)) { 215 processRight(childAst); 216 } 217 else if (!isAcceptableToken(childAst)) { 218 //Traverse all subtree tokens which will never be configured 219 //to be launched in visitToken() 220 processExpression(childAst); 221 } 222 childAst = childAst.getNextSibling(); 223 } 224 } 225 } 226 227 /** 228 * Checks whether AcceptableTokens contains the given ast. 229 * @param ast the token to check. 230 * @return true if the ast is in AcceptableTokens. 231 */ 232 private boolean isAcceptableToken(DetailAST ast) { 233 boolean result = false; 234 if (Arrays.binarySearch(acceptableTokens, ast.getType()) >= 0) { 235 result = true; 236 } 237 return result; 238 } 239 240 /** 241 * Returns array of acceptable tokens. 242 * @return acceptableTokens. 243 */ 244 private static int[] makeAcceptableTokens() { 245 return new int[] {TokenTypes.ANNOTATION, 246 TokenTypes.ANNOTATION_FIELD_DEF, 247 TokenTypes.CTOR_CALL, 248 TokenTypes.CTOR_DEF, 249 TokenTypes.DOT, 250 TokenTypes.ENUM_CONSTANT_DEF, 251 TokenTypes.EXPR, 252 TokenTypes.LITERAL_CATCH, 253 TokenTypes.LITERAL_DO, 254 TokenTypes.LITERAL_FOR, 255 TokenTypes.LITERAL_IF, 256 TokenTypes.LITERAL_NEW, 257 TokenTypes.LITERAL_SWITCH, 258 TokenTypes.LITERAL_SYNCHRONIZED, 259 TokenTypes.LITERAL_WHILE, 260 TokenTypes.METHOD_CALL, 261 TokenTypes.METHOD_DEF, 262 TokenTypes.QUESTION, 263 TokenTypes.RESOURCE_SPECIFICATION, 264 TokenTypes.SUPER_CTOR_CALL, 265 TokenTypes.LAMBDA, 266 }; 267 } 268 269 /** 270 * Checks whether {@link TokenTypes#RPAREN} is a closing paren 271 * of a {@link TokenTypes#TYPECAST}. 272 * @param ast of a {@link TokenTypes#RPAREN} to check. 273 * @return true if ast is a closing paren of a {@link TokenTypes#TYPECAST}. 274 */ 275 private static boolean isInTypecast(DetailAST ast) { 276 boolean result = false; 277 if (ast.getParent().getType() == TokenTypes.TYPECAST) { 278 final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN); 279 if (firstRparen.getLineNo() == ast.getLineNo() 280 && firstRparen.getColumnNo() == ast.getColumnNo()) { 281 result = true; 282 } 283 } 284 return result; 285 } 286 287 /** 288 * Checks that a token follows an empty for iterator. 289 * @param ast the token to check 290 * @return whether a token follows an empty for iterator 291 */ 292 private static boolean isFollowsEmptyForIterator(DetailAST ast) { 293 boolean result = false; 294 final DetailAST parent = ast.getParent(); 295 //Only traditional for statements are examined, not for-each statements 296 if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { 297 final DetailAST forIterator = 298 parent.findFirstToken(TokenTypes.FOR_ITERATOR); 299 result = forIterator.getChildCount() == 0; 300 } 301 return result; 302 } 303 304 /** 305 * Checks that a token precedes an empty for initializer. 306 * @param ast the token to check 307 * @return whether a token precedes an empty for initializer 308 */ 309 private static boolean isPrecedingEmptyForInit(DetailAST ast) { 310 boolean result = false; 311 final DetailAST parent = ast.getParent(); 312 //Only traditional for statements are examined, not for-each statements 313 if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { 314 final DetailAST forIterator = 315 parent.findFirstToken(TokenTypes.FOR_INIT); 316 result = forIterator.getChildCount() == 0; 317 } 318 return result; 319 } 320}