//////////////////////////////////////////////////////////////////////////////// // 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.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="SimpleAccesorNameNotationCheck"> < * 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> */ 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 void visitToken(DetailAST methodDef) { final String methodName = methodDef.findFirstToken(TokenTypes.IDENT).getText(); if (hasBody(methodDef) && !isMethodAtAnonymousClass(methodDef)) { if (methodName.startsWith(BOOLEAN_GETTER_PREFIX)) { if (!isGetterCorrect(methodDef, methodName.substring(BOOLEAN_GETTER_PREFIX.length()))) { log(methodDef.getLineNo(), MSG_KEY_GETTER); } } else if (methodName.startsWith(SETTER_PREFIX)) { if (!isSetterCorrect(methodDef, methodName.substring(SETTER_PREFIX.length()))) { log(methodDef.getLineNo(), MSG_KEY_SETTER); } } else if (methodName.startsWith(GETTER_PREFIX)) { if (!isGetterCorrect(methodDef, methodName.substring(GETTER_PREFIX.length()))) { log(methodDef.getLineNo(), 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.branchContains(TokenTypes.LITERAL_VOID)) { 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; final DetailAST assigningFirstChild = assign.getFirstChild(); if (assign.getChildCount() == 2 && assign.getLastChild().getType() == TokenTypes.IDENT) { if (assigningFirstChild.getType() == TokenTypes.IDENT) { nameOfSettingField = assigningFirstChild.getText(); if (checkNameOfParameters(parameters, nameOfSettingField)) { nameOfSettingField = null; } } else { if (assigningFirstChild.getType() == TokenTypes.DOT) { if ("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 * - DeailAST 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 paramrters * - 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 paramrters, String fieldName) { boolean isNameOfParameter = false; final int parametersChildCount = paramrters.getChildCount(); final DetailAST parameterDef = paramrters .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; } }