//////////////////////////////////////////////////////////////////////////////// // checkstyle: Checks Java source code for adherence to a set of rules. // Copyright (C) 2001-2017 the original author or authors. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// package com.puppycrawl.tools.checkstyle.checks.whitespace; import java.util.Arrays; import com.puppycrawl.tools.checkstyle.api.DetailAST; import com.puppycrawl.tools.checkstyle.api.TokenTypes; import com.puppycrawl.tools.checkstyle.utils.CommonUtils; /** * <p>Checks the padding of parentheses; that is whether a space is required * after a left parenthesis and before a right parenthesis, or such spaces are * forbidden, with the exception that it does * not check for padding of the right parenthesis at an empty for iterator and * empty for initializer. * Use Check {@link EmptyForIteratorPadCheck EmptyForIteratorPad} to validate * empty for iterators and {@link EmptyForInitializerPadCheck EmptyForInitializerPad} * to validate empty for initializers. Typecasts are also not checked, as there is * {@link TypecastParenPadCheck TypecastParenPad} to validate them. * </p> * <p> * The policy to verify is specified using the {@link PadOption} class and * defaults to {@link PadOption#NOSPACE}. * </p> * <p> By default the check will check parentheses that occur with the following * tokens: * {@link TokenTypes#ANNOTATION ANNOTATION}, * {@link TokenTypes#ANNOTATION_FIELD_DEF ANNOTATION_FIELD_DEF}, * {@link TokenTypes#CTOR_DEF CTOR_DEF}, * {@link TokenTypes#CTOR_CALL CTOR_CALL}, * {@link TokenTypes#DOT DOT}, * {@link TokenTypes#ENUM_CONSTANT_DEF ENUM_CONSTANT_DEF}, * {@link TokenTypes#EXPR EXPR}, * {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}, * {@link TokenTypes#LITERAL_DO LITERAL_DO}, * {@link TokenTypes#LITERAL_FOR LITERAL_FOR}, * {@link TokenTypes#LITERAL_IF LITERAL_IF}, * {@link TokenTypes#LITERAL_NEW LITERAL_NEW}, * {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH}, * {@link TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED}, * {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}, * {@link TokenTypes#METHOD_CALL METHOD_CALL}, * {@link TokenTypes#METHOD_DEF METHOD_DEF}, * {@link TokenTypes#RESOURCE_SPECIFICATION RESOURCE_SPECIFICATION}, * {@link TokenTypes#SUPER_CTOR_CALL SUPER_CTOR_CALL}, * {@link TokenTypes#QUESTION QUESTION}, * {@link TokenTypes#LAMBDA LAMBDA}, * </p> * <p> * An example of how to configure the check is: * </p> * <pre> * <module name="ParenPad"/> * </pre> * <p> * An example of how to configure the check to require spaces for the * parentheses of constructor, method, and super constructor invocations is: * </p> * <pre> * <module name="ParenPad"> * <property name="tokens" * value="CTOR_CALL, METHOD_CALL, SUPER_CTOR_CALL"/> * <property name="option" value="space"/> * </module> * </pre> * @author Oliver Burn * @author Vladislav Lisetskiy */ public class ParenPadCheck extends AbstractParenPadCheck { /** * The array of Acceptable Tokens. */ private final int[] acceptableTokens; /** * Initializes and sorts acceptableTokens to make binary search over it possible. */ public ParenPadCheck() { acceptableTokens = makeAcceptableTokens(); Arrays.sort(acceptableTokens); } @Override public int[] getDefaultTokens() { return makeAcceptableTokens(); } @Override public int[] getAcceptableTokens() { return makeAcceptableTokens(); } @Override public int[] getRequiredTokens() { return CommonUtils.EMPTY_INT_ARRAY; } @Override public void visitToken(DetailAST ast) { switch (ast.getType()) { case TokenTypes.METHOD_CALL: processLeft(ast); processRight(ast.findFirstToken(TokenTypes.RPAREN)); processExpression(ast); break; case TokenTypes.DOT: case TokenTypes.EXPR: case TokenTypes.QUESTION: processExpression(ast); break; case TokenTypes.LITERAL_FOR: visitLiteralFor(ast); break; case TokenTypes.ANNOTATION: case TokenTypes.ENUM_CONSTANT_DEF: case TokenTypes.LITERAL_NEW: case TokenTypes.LITERAL_SYNCHRONIZED: case TokenTypes.LAMBDA: visitTokenWithOptionalParentheses(ast); break; default: processLeft(ast.findFirstToken(TokenTypes.LPAREN)); processRight(ast.findFirstToken(TokenTypes.RPAREN)); } } /** * Checks parens in token which may not contain parens, e.g. * {@link TokenTypes#ENUM_CONSTANT_DEF}, {@link TokenTypes#ANNOTATION} * {@link TokenTypes#LITERAL_SYNCHRONIZED}, {@link TokenTypes#LITERAL_NEW} and * {@link TokenTypes#LAMBDA}. * @param ast the token to check. */ private void visitTokenWithOptionalParentheses(DetailAST ast) { final DetailAST parenAst = ast.findFirstToken(TokenTypes.LPAREN); if (parenAst != null) { processLeft(parenAst); processRight(ast.findFirstToken(TokenTypes.RPAREN)); } } /** * Checks parens in {@link TokenTypes#LITERAL_FOR}. * @param ast the token to check. */ private void visitLiteralFor(DetailAST ast) { final DetailAST lparen = ast.findFirstToken(TokenTypes.LPAREN); if (!isPrecedingEmptyForInit(lparen)) { processLeft(lparen); } final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN); if (!isFollowsEmptyForIterator(rparen)) { processRight(rparen); } } /** * Checks parens inside {@link TokenTypes#EXPR}, {@link TokenTypes#QUESTION} * and {@link TokenTypes#METHOD_CALL}. * @param ast the token to check. */ private void processExpression(DetailAST ast) { if (ast.branchContains(TokenTypes.LPAREN)) { DetailAST childAst = ast.getFirstChild(); while (childAst != null) { if (childAst.getType() == TokenTypes.LPAREN) { processLeft(childAst); processExpression(childAst); } else if (childAst.getType() == TokenTypes.RPAREN && !isInTypecast(childAst)) { processRight(childAst); } else if (!isAcceptableToken(childAst)) { //Traverse all subtree tokens which will never be configured //to be launched in visitToken() processExpression(childAst); } childAst = childAst.getNextSibling(); } } } /** * Checks whether AcceptableTokens contains the given ast. * @param ast the token to check. * @return true if the ast is in AcceptableTokens. */ private boolean isAcceptableToken(DetailAST ast) { boolean result = false; if (Arrays.binarySearch(acceptableTokens, ast.getType()) >= 0) { result = true; } return result; } /** * @return acceptableTokens. */ private static int[] makeAcceptableTokens() { return new int[] {TokenTypes.ANNOTATION, TokenTypes.ANNOTATION_FIELD_DEF, TokenTypes.CTOR_CALL, TokenTypes.CTOR_DEF, TokenTypes.DOT, TokenTypes.ENUM_CONSTANT_DEF, TokenTypes.EXPR, TokenTypes.LITERAL_CATCH, TokenTypes.LITERAL_DO, TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_IF, TokenTypes.LITERAL_NEW, TokenTypes.LITERAL_SWITCH, TokenTypes.LITERAL_SYNCHRONIZED, TokenTypes.LITERAL_WHILE, TokenTypes.METHOD_CALL, TokenTypes.METHOD_DEF, TokenTypes.QUESTION, TokenTypes.RESOURCE_SPECIFICATION, TokenTypes.SUPER_CTOR_CALL, TokenTypes.LAMBDA, }; } /** * Checks whether {@link TokenTypes#RPAREN} is a closing paren * of a {@link TokenTypes#TYPECAST}. * @param ast of a {@link TokenTypes#RPAREN} to check. * @return true if ast is a closing paren of a {@link TokenTypes#TYPECAST}. */ private static boolean isInTypecast(DetailAST ast) { boolean result = false; if (ast.getParent().getType() == TokenTypes.TYPECAST) { final DetailAST firstRparen = ast.getParent().findFirstToken(TokenTypes.RPAREN); if (firstRparen.getLineNo() == ast.getLineNo() && firstRparen.getColumnNo() == ast.getColumnNo()) { result = true; } } return result; } /** * Checks that a token follows an empty for iterator. * @param ast the token to check * @return whether a token follows an empty for iterator */ private static boolean isFollowsEmptyForIterator(DetailAST ast) { boolean result = false; final DetailAST parent = ast.getParent(); //Only traditional for statements are examined, not for-each statements if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { final DetailAST forIterator = parent.findFirstToken(TokenTypes.FOR_ITERATOR); result = forIterator.getChildCount() == 0; } return result; } /** * Checks that a token precedes an empty for initializer. * @param ast the token to check * @return whether a token precedes an empty for initializer */ private static boolean isPrecedingEmptyForInit(DetailAST ast) { boolean result = false; final DetailAST parent = ast.getParent(); //Only traditional for statements are examined, not for-each statements if (parent.findFirstToken(TokenTypes.FOR_EACH_CLAUSE) == null) { final DetailAST forIterator = parent.findFirstToken(TokenTypes.FOR_INIT); result = forIterator.getChildCount() == 0; } return result; } }