SimpleAccessorNameNotationCheck.java
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2019 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.github.sevntu.checkstyle.checks.coding;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
/**
* <p>
* This check verify incorrect name of setter and getter methods if it used
* field with other name.
* </p>
* <p>
* For example, method has name 'setXXX', but define field with name 'YYY'.
* Setter and getter methods must have next view: XXXType getXXXName() {return
* XXXName} XXXType getXXXName() {return this.XXXName} void setXXXName(XXXType
* value) { this.XXXName = value} void setXXXName(XXXType value) { XXXName =
* value}
* </p>
* <p>If name of field contains prefix,then must to be define parameter 'prefix'
* , for example:</p>
*
* <pre>
* <module name="SimpleAccessorNameNotationCheck"> <
* property name="prefix" value="m_"/>
* </module>
* </pre>
*
*
* @author <a href="mailto:hidoyatov.v.i@gmail.com">Hidoyatov Victor</a>
* @author <a href="mailto:iliadubinin91@gmail.com">Ilja Dubinin</a>
* @since 1.9.0
*/
public class SimpleAccessorNameNotationCheck extends AbstractCheck {
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY_GETTER = "incorrect.getter.name";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY_SETTER = "incorrect.setter.name";
/** Prefix for boolean getter methods. */
private static final String BOOLEAN_GETTER_PREFIX = "is";
/** Prefix for non-boolean getter methods. */
private static final String GETTER_PREFIX = "get";
/** Prefix for setter methods. */
private static final String SETTER_PREFIX = "set";
/**
* Number of children in expression only block. Expecting three children:
* EXPR, SEMI and RCURLY.
*/
private static final int EXPRESSION_BLOCK_CHILD_COUNT = 3;
/**
* Prefix of field's name.
*/
private String prefix = "";
/**
* Setter for prefix.
* @param prefix
* - prefix of field's name
*/
public void setPrefix(String prefix) {
this.prefix = prefix;
}
@Override
public int[] getDefaultTokens() {
return new int[] {
TokenTypes.METHOD_DEF,
};
}
@Override
public int[] getAcceptableTokens() {
return getDefaultTokens();
}
@Override
public int[] getRequiredTokens() {
return getDefaultTokens();
}
@Override
public void visitToken(DetailAST methodDef) {
if (hasBody(methodDef) && !isMethodAtAnonymousClass(methodDef)) {
final String methodName = methodDef.findFirstToken(TokenTypes.IDENT).getText();
if (methodName.startsWith(BOOLEAN_GETTER_PREFIX)) {
if (!isGetterCorrect(methodDef,
methodName.substring(BOOLEAN_GETTER_PREFIX.length()))) {
log(methodDef, MSG_KEY_GETTER);
}
}
else if (methodName.startsWith(SETTER_PREFIX)) {
if (!isSetterCorrect(methodDef, methodName.substring(SETTER_PREFIX.length()))) {
log(methodDef, MSG_KEY_SETTER);
}
}
else if (methodName.startsWith(GETTER_PREFIX)
&& !isGetterCorrect(methodDef, methodName.substring(GETTER_PREFIX.length()))) {
log(methodDef, MSG_KEY_GETTER);
}
}
}
/**
* <p>
* Returns true when setter is correct.
* </p>
* @param methodDef
* - DetailAST contains method definition.
* @param methodName
* - name of setter without "set".
* @return true when setter is correct.
*/
private boolean isSetterCorrect(DetailAST methodDef, String methodName) {
final DetailAST methodType = methodDef.findFirstToken(TokenTypes.TYPE);
boolean result = true;
if (methodType.findFirstToken(TokenTypes.LITERAL_VOID) != null) {
DetailAST currentVerifiedTop = methodDef.findFirstToken(TokenTypes.SLIST);
if (containsOnlyExpression(currentVerifiedTop)) {
currentVerifiedTop = currentVerifiedTop.getFirstChild();
final boolean containsOnlyOneAssignment = currentVerifiedTop.getFirstChild()
.getType() == TokenTypes.ASSIGN;
if (containsOnlyOneAssignment) {
currentVerifiedTop = currentVerifiedTop.getFirstChild();
final DetailAST parameters =
methodDef.findFirstToken(TokenTypes.PARAMETERS);
final String nameOfSettingField = getNameOfSettingField(
currentVerifiedTop, parameters);
if (nameOfSettingField != null
&& verifyFieldAndMethodName(nameOfSettingField,
methodName)) {
result = false;
}
}
}
}
return result;
}
/**
* <p>
* Returns true when getter is correct.
* </p>
* .
* @param methodDef
* - DetailAST contains method definition.
* @param methodName
* - name of getter without "get" or "is".
* @return true when getter is correct.
*/
private boolean isGetterCorrect(DetailAST methodDef, String methodName) {
final DetailAST parameters = methodDef.findFirstToken(TokenTypes.PARAMETERS);
boolean result = true;
if (parameters.getChildCount() == 0) {
DetailAST currentVerifiedTop =
methodDef.findFirstToken(TokenTypes.SLIST);
if (containsOnlyReturn(currentVerifiedTop)) {
currentVerifiedTop = currentVerifiedTop.getFirstChild();
if (isCorrectReturn(currentVerifiedTop)) {
currentVerifiedTop = currentVerifiedTop.getFirstChild();
final String nameOfGettingField = getNameOfGettingField(currentVerifiedTop);
if (nameOfGettingField != null
&& verifyFieldAndMethodName(nameOfGettingField,
methodName)) {
result = false;
}
}
}
}
return result;
}
/**
* <p>
* Returns true, when object block contains only three child: EXPR, SEMI and
* RCURLY.
* </p>
* @param objectBlock
* - is a link to checked block
* @return true if object block is correct
*/
private static boolean containsOnlyExpression(DetailAST objectBlock) {
//three child: EXPR, SEMI and RCURLY
return objectBlock.getChildCount() == EXPRESSION_BLOCK_CHILD_COUNT
&& objectBlock.getFirstChild().getType() == TokenTypes.EXPR;
}
/**
* <p>
* Return name of the field, that use in the setter.
* </p>
* @param assign
* - DetailAST contains ASSIGN from EXPR of the setter.
* @param parameters
* - DetailAST contains parameters of the setter.
* @return name of field, that use in setter.
*/
private static String getNameOfSettingField(DetailAST assign,
DetailAST parameters) {
String nameOfSettingField = null;
if (assign.getChildCount() == 2
&& assign.getLastChild().getType() == TokenTypes.IDENT) {
final DetailAST assigningFirstChild = assign.getFirstChild();
if (assigningFirstChild.getType() == TokenTypes.IDENT) {
nameOfSettingField = assigningFirstChild.getText();
if (checkNameOfParameters(parameters, nameOfSettingField)) {
nameOfSettingField = null;
}
}
else if (assigningFirstChild.getType() == TokenTypes.DOT
&& "this".equals(assigningFirstChild.getFirstChild().getText())) {
nameOfSettingField = assigningFirstChild.getLastChild()
.getText();
}
}
return nameOfSettingField;
}
/**
* <p>
* Compare name of the field and part of name of the method. Return true
* when they are different.
* </p>
* @param fieldName
* - name of the field.
* @param methodName
* - part of name of the method (without "set", "get" or "is").
* @return true when names are different.
*/
private boolean verifyFieldAndMethodName(String fieldName,
String methodName) {
final String name = prefix + methodName;
return !fieldName.equalsIgnoreCase(name);
}
/**
* <p>
* Returns true, when object block contains only one child: LITERAL_RETURN.
* </p>
* @param methodBody
* - DetailAST contains object block of the getter.
* @return true when object block correct.
*/
private static boolean containsOnlyReturn(DetailAST methodBody) {
return methodBody.getFirstChild().getType() == TokenTypes.LITERAL_RETURN;
}
/**
* <p>
* Return true when getter has correct arguments of return.
* </p>
* @param literalReturn
* - DetailAST contains LITERAL_RETURN
* @return - true when getter has correct return.
*/
private static boolean isCorrectReturn(DetailAST literalReturn) {
//two child: EXPR and SEMI
return literalReturn.getChildCount() == 2
&& literalReturn.getFirstChild().getType() == TokenTypes.EXPR;
}
/**
* <p>
* Return name of the field, that use in the getter.
* </p>
* @param expr
* - DetailAST contains expression from getter.
* @return name of the field, that use in getter.
*/
private static String getNameOfGettingField(DetailAST expr) {
String nameOfGettingField = null;
if (expr.getChildCount() == 1) {
final DetailAST exprFirstChild = expr.getFirstChild();
if (exprFirstChild.getType() == TokenTypes.IDENT) {
nameOfGettingField = exprFirstChild.getText();
}
else {
if (exprFirstChild.getType() == TokenTypes.DOT
&& exprFirstChild.getChildCount() == 2
&& exprFirstChild.getFirstChild().getType() == TokenTypes.LITERAL_THIS) {
nameOfGettingField = exprFirstChild.getLastChild().getText();
}
}
}
return nameOfGettingField;
}
/**
* <p>
* Return true when name of the field is not contained in parameters of the
* setter method.
* </p>
* @param parameters
* - DetailAST contains parameters of the setter.
* @param fieldName
* - name of the field.
* @return true when name of the field is not contained in parameters.
*/
private static boolean checkNameOfParameters(DetailAST parameters,
String fieldName) {
boolean isNameOfParameter = false;
final int parametersChildCount = parameters.getChildCount();
final DetailAST parameterDef = parameters
.findFirstToken(TokenTypes.PARAMETER_DEF);
for (int i = 0; i < parametersChildCount && !isNameOfParameter; i++) {
isNameOfParameter = parameterDef.findFirstToken(TokenTypes.IDENT).getText()
.equals(fieldName);
}
return isNameOfParameter;
}
/**
* <p>
* Returns true when method has contained into an anonymous class.
* </p>
* @param methodDef the METHOD_DEF token.
* @return true when method has contained into an anonymous class.
*/
private static boolean isMethodAtAnonymousClass(DetailAST methodDef) {
final DetailAST classObjBlock = methodDef.getParent();
return classObjBlock.getParent().getType() == TokenTypes.LITERAL_NEW;
}
/**
* <p>
* Returns true when method or other block has a body.
* </p>
* @param methodDef
* - method definition node
* @return true when method or other block has a body.
*/
private static boolean hasBody(DetailAST methodDef) {
final DetailAST body = methodDef.findFirstToken(TokenTypes.SLIST);
return body != null;
}
}