////////////////////////////////////////////////////////////////////////////////
// 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.blocks;
import java.util.Locale;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.Scope;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.CheckUtils;
import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
/**
* <p>
* Checks the placement of right curly braces.
* The policy to verify is specified using the {@link RightCurlyOption} class
* and defaults to {@link RightCurlyOption#SAME}.
* </p>
* <p> By default the check will check the following tokens:
* {@link TokenTypes#LITERAL_TRY LITERAL_TRY},
* {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH},
* {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY},
* {@link TokenTypes#LITERAL_IF LITERAL_IF},
* {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE}.
* Other acceptable tokens are:
* {@link TokenTypes#CLASS_DEF CLASS_DEF},
* {@link TokenTypes#METHOD_DEF METHOD_DEF},
* {@link TokenTypes#CTOR_DEF CTOR_DEF}.
* {@link TokenTypes#LITERAL_FOR LITERAL_FOR}.
* {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}.
* {@link TokenTypes#LITERAL_DO LITERAL_DO}.
* {@link TokenTypes#STATIC_INIT STATIC_INIT}.
* {@link TokenTypes#INSTANCE_INIT INSTANCE_INIT}.
* {@link TokenTypes#LAMBDA LAMBDA}.
* </p>
* <p>
* <b>shouldStartLine</b> - does the check need to check
* if right curly starts line. Default value is <b>true</b>
* </p>
* <p>
* An example of how to configure the check is:
* </p>
* <pre>
* <module name="RightCurly"/>
* </pre>
* <p>
* An example of how to configure the check with policy
* {@link RightCurlyOption#ALONE} for {@code else} and
* {@code {@link TokenTypes#METHOD_DEF METHOD_DEF}}tokens is:
* </p>
* <pre>
* <module name="RightCurly">
* <property name="tokens" value="LITERAL_ELSE"/>
* <property name="option" value="alone"/>
* </module>
* </pre>
*
* @author Oliver Burn
* @author lkuehne
* @author o_sukhodolsky
* @author maxvetrenko
* @author Andrei Selkin
* @author <a href="mailto:piotr.listkiewicz@gmail.com">liscju</a>
*/
public class RightCurlyCheck extends AbstractCheck {
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY_LINE_BREAK_BEFORE = "line.break.before";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY_LINE_ALONE = "line.alone";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY_LINE_SAME = "line.same";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY_LINE_NEW = "line.new";
/** Do we need to check if right curly starts line. */
private boolean shouldStartLine = true;
/** The policy to enforce. */
private RightCurlyOption option = RightCurlyOption.SAME;
/**
* Sets the option to enforce.
* @param optionStr string to decode option from
* @throws IllegalArgumentException if unable to decode
*/
public void setOption(String optionStr) {
try {
option = RightCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
}
catch (IllegalArgumentException iae) {
throw new IllegalArgumentException("unable to parse " + optionStr, iae);
}
}
/**
* Does the check need to check if right curly starts line.
* @param flag new value of this property.
*/
public void setShouldStartLine(boolean flag) {
shouldStartLine = flag;
}
@Override
public int[] getDefaultTokens() {
return new int[] {
TokenTypes.LITERAL_TRY,
TokenTypes.LITERAL_CATCH,
TokenTypes.LITERAL_FINALLY,
TokenTypes.LITERAL_IF,
TokenTypes.LITERAL_ELSE,
};
}
@Override
public int[] getAcceptableTokens() {
return new int[] {
TokenTypes.LITERAL_TRY,
TokenTypes.LITERAL_CATCH,
TokenTypes.LITERAL_FINALLY,
TokenTypes.LITERAL_IF,
TokenTypes.LITERAL_ELSE,
TokenTypes.CLASS_DEF,
TokenTypes.METHOD_DEF,
TokenTypes.CTOR_DEF,
TokenTypes.LITERAL_FOR,
TokenTypes.LITERAL_WHILE,
TokenTypes.LITERAL_DO,
TokenTypes.STATIC_INIT,
TokenTypes.INSTANCE_INIT,
TokenTypes.LAMBDA,
};
}
@Override
public int[] getRequiredTokens() {
return CommonUtils.EMPTY_INT_ARRAY;
}
@Override
public void visitToken(DetailAST ast) {
final Details details = Details.getDetails(ast);
final DetailAST rcurly = details.rcurly;
if (rcurly != null) {
final String violation = validate(details);
if (!violation.isEmpty()) {
log(rcurly, violation, "}", rcurly.getColumnNo() + 1);
}
}
}
/**
* Does general validation.
* @param details for validation.
* @return violation message or empty string
* if there was not violation during validation.
*/
private String validate(Details details) {
String violation = "";
if (shouldHaveLineBreakBefore(option, details)) {
violation = MSG_KEY_LINE_BREAK_BEFORE;
}
else if (shouldBeOnSameLine(option, details)) {
violation = MSG_KEY_LINE_SAME;
}
else if (shouldBeAloneOnLine(option, details)) {
violation = MSG_KEY_LINE_ALONE;
}
else if (shouldStartLine) {
final String targetSourceLine = getLines()[details.rcurly.getLineNo() - 1];
if (!isOnStartOfLine(details, targetSourceLine)) {
violation = MSG_KEY_LINE_NEW;
}
}
return violation;
}
/**
* Checks whether a right curly should have a line break before.
* @param bracePolicy option for placing the right curly brace.
* @param details details for validation.
* @return true if a right curly should have a line break before.
*/
private static boolean shouldHaveLineBreakBefore(RightCurlyOption bracePolicy,
Details details) {
return bracePolicy == RightCurlyOption.SAME
&& !hasLineBreakBefore(details.rcurly)
&& details.lcurly.getLineNo() != details.rcurly.getLineNo();
}
/**
* Checks that a right curly should be on the same line as the next statement.
* @param bracePolicy option for placing the right curly brace
* @param details Details for validation
* @return true if a right curly should be alone on a line.
*/
private static boolean shouldBeOnSameLine(RightCurlyOption bracePolicy, Details details) {
return bracePolicy == RightCurlyOption.SAME
&& !details.shouldCheckLastRcurly
&& details.rcurly.getLineNo() != details.nextToken.getLineNo();
}
/**
* Checks that a right curly should be alone on a line.
* @param bracePolicy option for placing the right curly brace
* @param details Details for validation
* @return true if a right curly should be alone on a line.
*/
private static boolean shouldBeAloneOnLine(RightCurlyOption bracePolicy, Details details) {
return bracePolicy == RightCurlyOption.ALONE
&& shouldBeAloneOnLineWithAloneOption(details)
|| bracePolicy == RightCurlyOption.ALONE_OR_SINGLELINE
&& shouldBeAloneOnLineWithAloneOrSinglelineOption(details)
|| details.shouldCheckLastRcurly
&& details.rcurly.getLineNo() == details.nextToken.getLineNo();
}
/**
* Whether right curly should be alone on line when ALONE option is used.
* @param details details for validation.
* @return true, if right curly should be alone on line when ALONE option is used.
*/
private static boolean shouldBeAloneOnLineWithAloneOption(Details details) {
return !isAloneOnLine(details)
&& !isEmptyBody(details.lcurly);
}
/**
* Whether right curly should be alone on line when ALONE_OR_SINGLELINE option is used.
* @param details details for validation.
* @return true, if right curly should be alone on line
* when ALONE_OR_SINGLELINE option is used.
*/
private static boolean shouldBeAloneOnLineWithAloneOrSinglelineOption(Details details) {
return !isAloneOnLine(details)
&& !isSingleLineBlock(details)
&& !isAnonInnerClassInit(details.lcurly)
&& !isEmptyBody(details.lcurly);
}
/**
* Whether right curly brace starts target source line.
* @param details Details of right curly brace for validation
* @param targetSourceLine source line to check
* @return true if right curly brace starts target source line.
*/
private static boolean isOnStartOfLine(Details details, String targetSourceLine) {
return CommonUtils.hasWhitespaceBefore(details.rcurly.getColumnNo(), targetSourceLine)
|| details.lcurly.getLineNo() == details.rcurly.getLineNo();
}
/**
* Checks whether right curly is alone on a line.
* @param details for validation.
* @return true if right curly is alone on a line.
*/
private static boolean isAloneOnLine(Details details) {
final DetailAST rcurly = details.rcurly;
final DetailAST lcurly = details.lcurly;
final DetailAST nextToken = details.nextToken;
return rcurly.getLineNo() != lcurly.getLineNo()
&& rcurly.getLineNo() != nextToken.getLineNo();
}
/**
* Checks whether block has a single-line format.
* @param details for validation.
* @return true if block has single-line format.
*/
private static boolean isSingleLineBlock(Details details) {
final DetailAST rcurly = details.rcurly;
final DetailAST lcurly = details.lcurly;
final DetailAST nextToken = details.nextToken;
return rcurly.getLineNo() == lcurly.getLineNo()
&& rcurly.getLineNo() != nextToken.getLineNo();
}
/**
* Checks whether lcurly is in anonymous inner class initialization.
* @param lcurly left curly token.
* @return true if lcurly begins anonymous inner class initialization.
*/
private static boolean isAnonInnerClassInit(DetailAST lcurly) {
final Scope surroundingScope = ScopeUtils.getSurroundingScope(lcurly);
return surroundingScope.ordinal() == Scope.ANONINNER.ordinal();
}
/**
* Checks if definition body is empty.
* @param lcurly left curly.
* @return true if definition body is empty.
*/
private static boolean isEmptyBody(DetailAST lcurly) {
boolean result = false;
if (lcurly.getParent().getType() == TokenTypes.OBJBLOCK) {
if (lcurly.getNextSibling().getType() == TokenTypes.RCURLY) {
result = true;
}
}
else if (lcurly.getFirstChild().getType() == TokenTypes.RCURLY) {
result = true;
}
return result;
}
/**
* Checks if right curly has line break before.
* @param rightCurly right curly token.
* @return true, if right curly has line break before.
*/
private static boolean hasLineBreakBefore(DetailAST rightCurly) {
final DetailAST previousToken = rightCurly.getPreviousSibling();
return previousToken == null
|| rightCurly.getLineNo() != previousToken.getLineNo();
}
/**
* Structure that contains all details for validation.
*/
private static final class Details {
/** Right curly. */
private final DetailAST rcurly;
/** Left curly. */
private final DetailAST lcurly;
/** Next token. */
private final DetailAST nextToken;
/** Should check last right curly. */
private final boolean shouldCheckLastRcurly;
/**
* Constructor.
* @param lcurly the lcurly of the token whose details are being collected
* @param rcurly the rcurly of the token whose details are being collected
* @param nextToken the token after the token whose details are being collected
* @param shouldCheckLastRcurly boolean value to determine if to check last rcurly
*/
private Details(DetailAST lcurly, DetailAST rcurly,
DetailAST nextToken, boolean shouldCheckLastRcurly) {
this.lcurly = lcurly;
this.rcurly = rcurly;
this.nextToken = nextToken;
this.shouldCheckLastRcurly = shouldCheckLastRcurly;
}
/**
* Collects validation Details.
* @param ast a {@code DetailAST} value
* @return object containing all details to make a validation
*/
private static Details getDetails(DetailAST ast) {
final Details details;
switch (ast.getType()) {
case TokenTypes.LITERAL_TRY:
case TokenTypes.LITERAL_CATCH:
case TokenTypes.LITERAL_FINALLY:
details = getDetailsForTryCatchFinally(ast);
break;
case TokenTypes.LITERAL_IF:
case TokenTypes.LITERAL_ELSE:
details = getDetailsForIfElse(ast);
break;
case TokenTypes.LITERAL_DO:
case TokenTypes.LITERAL_WHILE:
case TokenTypes.LITERAL_FOR:
details = getDetailsForLoops(ast);
break;
case TokenTypes.LAMBDA:
details = getDetailsForLambda(ast);
break;
default:
details = getDetailsForOthers(ast);
break;
}
return details;
}
/**
* Collects validation details for LITERAL_TRY, LITERAL_CATCH, and LITERAL_FINALLY.
* @param ast a {@code DetailAST} value
* @return object containing all details to make a validation
*/
private static Details getDetailsForTryCatchFinally(DetailAST ast) {
boolean shouldCheckLastRcurly = false;
final DetailAST rcurly;
final DetailAST lcurly;
DetailAST nextToken;
final int tokenType = ast.getType();
if (tokenType == TokenTypes.LITERAL_TRY) {
if (ast.getFirstChild().getType() == TokenTypes.RESOURCE_SPECIFICATION) {
lcurly = ast.getFirstChild().getNextSibling();
}
else {
lcurly = ast.getFirstChild();
}
nextToken = lcurly.getNextSibling();
rcurly = lcurly.getLastChild();
if (nextToken == null) {
shouldCheckLastRcurly = true;
nextToken = getNextToken(ast);
}
}
else if (tokenType == TokenTypes.LITERAL_CATCH) {
nextToken = ast.getNextSibling();
lcurly = ast.getLastChild();
rcurly = lcurly.getLastChild();
if (nextToken == null) {
shouldCheckLastRcurly = true;
nextToken = getNextToken(ast);
}
}
else {
shouldCheckLastRcurly = true;
nextToken = getNextToken(ast);
lcurly = ast.getFirstChild();
rcurly = lcurly.getLastChild();
}
return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
}
/**
* Collects validation details for LITERAL_IF and LITERAL_ELSE.
* @param ast a {@code DetailAST} value
* @return object containing all details to make a validation
*/
private static Details getDetailsForIfElse(DetailAST ast) {
boolean shouldCheckLastRcurly = false;
DetailAST rcurly = null;
final DetailAST lcurly;
DetailAST nextToken;
final int tokenType = ast.getType();
if (tokenType == TokenTypes.LITERAL_IF) {
nextToken = ast.findFirstToken(TokenTypes.LITERAL_ELSE);
if (nextToken == null) {
shouldCheckLastRcurly = true;
nextToken = getNextToken(ast);
lcurly = ast.getLastChild();
}
else {
lcurly = nextToken.getPreviousSibling();
}
if (lcurly.getType() == TokenTypes.SLIST) {
rcurly = lcurly.getLastChild();
}
}
else {
shouldCheckLastRcurly = true;
nextToken = getNextToken(ast);
lcurly = ast.getFirstChild();
if (lcurly.getType() == TokenTypes.SLIST) {
rcurly = lcurly.getLastChild();
}
}
return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
}
/**
* Collects validation details for CLASS_DEF, METHOD DEF, CTOR_DEF, STATIC_INIT, and
* INSTANCE_INIT.
* @param ast a {@code DetailAST} value
* @return an object containing all details to make a validation
*/
private static Details getDetailsForOthers(DetailAST ast) {
DetailAST rcurly = null;
final DetailAST lcurly;
final DetailAST nextToken;
final int tokenType = ast.getType();
if (tokenType == TokenTypes.CLASS_DEF) {
final DetailAST child = ast.getLastChild();
lcurly = child.getFirstChild();
rcurly = child.getLastChild();
nextToken = ast;
}
else if (tokenType == TokenTypes.METHOD_DEF) {
lcurly = ast.findFirstToken(TokenTypes.SLIST);
if (lcurly != null) {
// SLIST could be absent if method is abstract
rcurly = lcurly.getLastChild();
}
nextToken = getNextToken(ast);
}
else {
lcurly = ast.findFirstToken(TokenTypes.SLIST);
rcurly = lcurly.getLastChild();
nextToken = getNextToken(ast);
}
return new Details(lcurly, rcurly, nextToken, false);
}
/**
* Collects validation details for loops' tokens.
* @param ast a {@code DetailAST} value
* @return an object containing all details to make a validation
*/
private static Details getDetailsForLoops(DetailAST ast) {
DetailAST rcurly = null;
final DetailAST lcurly;
final DetailAST nextToken;
final int tokenType = ast.getType();
if (tokenType == TokenTypes.LITERAL_DO) {
nextToken = ast.findFirstToken(TokenTypes.DO_WHILE);
lcurly = ast.findFirstToken(TokenTypes.SLIST);
if (lcurly != null) {
rcurly = lcurly.getLastChild();
}
}
else {
lcurly = ast.findFirstToken(TokenTypes.SLIST);
if (lcurly != null) {
// SLIST could be absent in code like "while(true);"
rcurly = lcurly.getLastChild();
}
nextToken = getNextToken(ast);
}
return new Details(lcurly, rcurly, nextToken, false);
}
/**
* Collects validation details for Lambdas.
* @param ast a {@code DetailAST} value
* @return an object containing all details to make a validation
*/
private static Details getDetailsForLambda(DetailAST ast) {
final DetailAST lcurly = ast.findFirstToken(TokenTypes.SLIST);
boolean shouldCheckLastRcurly = false;
DetailAST nextToken = getNextToken(ast);
if (nextToken.getType() != TokenTypes.RPAREN
&& nextToken.getType() != TokenTypes.COMMA) {
shouldCheckLastRcurly = true;
nextToken = getNextToken(nextToken);
}
DetailAST rcurly = null;
if (lcurly != null) {
rcurly = lcurly.getLastChild();
}
return new Details(lcurly, rcurly, nextToken, shouldCheckLastRcurly);
}
/**
* Finds next token after the given one.
* @param ast the given node.
* @return the token which represents next lexical item.
*/
private static DetailAST getNextToken(DetailAST ast) {
DetailAST next = null;
DetailAST parent = ast;
while (next == null) {
next = parent.getNextSibling();
parent = parent.getParent();
}
return CheckUtils.getFirstNode(next);
}
}
}