//////////////////////////////////////////////////////////////////////////////// // 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 java.beans.Introspector; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Deque; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import com.puppycrawl.tools.checkstyle.api.AbstractCheck; import com.puppycrawl.tools.checkstyle.api.DetailAST; import com.puppycrawl.tools.checkstyle.api.TokenTypes; /** * <p> * Checks that the parts of a class(main, nested, member inner) declaration * appear in the rules order set by user using regular expressions. * <p> * The check forms line which consists of class member annotations, modifiers, * type and name from your code and compares it with your RegExp. * </p> * The rule consists of: * * <pre> * ClassMember(RegExp) * </pre> * To set class order use the following notation of the class members (case * insensitive): * <ol> * <li>"Field" to denote the Fields</li> * <li>"DeclareAnnonClassField" to denote the fields keeping objects of anonymous classes</li> * <li>"Ctor" to denote the Constructors</li> * <li>"Method" to denote the Methods</li> * <li>"GetterSetter" to denote the group of getter and setter methods</li> * <li>"MainMethod" to denote the main method</li> * <li>"InnerClass" to denote the Inner Classes</li> * <li>"InnerInterface" to denote the Inner Interfaces</li> * <li>"InnerEnum" to denote the Inner Enums</li> * </ol> * RegExp can include: * <ol> * <li>Annotations</li> * <li>Modifiers(public, protected, private, abstract, static, * final)</li> * <li>Type</li> * <li>Name</li> * </ol> * ATTENTION! * <p> * Use separator <code>' ', '.', '\s'</code> between declaration in the RegExp. * Whitespace should be added after each modifier. * </p> * <pre> * Example: * Field(public .*final .*) * Field(public final .*) * Field(public<code>\s*</code>final .*) * </pre> * NOTICE! * <p> * It is important to write exact order of modifiers in rules. So rule * <code><i>Field(public final)</i></code> does not match to * <code><i>final public value;</i></code>. * <a href='http://checkstyle.sourceforge.net/config_modifier.html#ModifierOrder'> * ModifierOrderCheck</a> * is recommended to use. * </p> * <p> * If you set empty RegExp e.g. <code>Field()</code>, it means that class member * doesn't have modifiers(default modifier) and checking the type and name of * member doesn't occur. * </p> * <p> * Between the declaration of a array and generic can't be whitespaces. * E.g.: <code>ArrayList<String[]> someName</code> * </p> * <p> * Use the separator '###' between the class declarations. * </p> * <p> * For Example: * </p> * <p> * <code>Field(private static final long serialVersionUID) ### * Field(public static final .*) ### Field(.*private .*) ### Ctor(.*) ### * GetterSetter(.*) ### Method(.*public .*final .*|@Ignore.*public .*) ### * Method(public static .*(final|(new|edit|create).*).*) ### * InnerClass(public abstract .*) ### InnerInterface(.*) ### InnerEnum(.*)</code> * </p> * * <p><b>What is group of getters and setters(<code>GetterSetter</code>)?</b></p> * <p> * It is ordered sequence of getters and setters like: * <pre> * public int getValue() { * log.info("Getting value"); * return value; * } * * public void setValue(int newValue) { * value = newValue; * } * * public Object getObj() { * return obj; * } * * public void setObj(Object obj) { * if (obj != null) { * this.obj = obj; * } else { * throw new IllegalArgumentException("Null value"); * } * } * * ... * </pre> * <p>Getter is public method that returns class field. Name of getter should be * 'get<i>FieldName</i>' in camel case.</p> * <p>Setter is public method with one parameter that assigns this parameter to class field. * Name of setter should be 'set<i>FieldName</i>' in camel case.</p> * <p>Setter of field X should be right after getter of field X.</p> * * @author <a href="mailto:solid.danil@gmail.com">Danil Lopatin</a> * @author <a href="mailto:barataliba@gmail.com">Baratali Izmailov</a> */ public class CustomDeclarationOrderCheck extends AbstractCheck { /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_KEY_FIELD = "custom.declaration.order.field"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_KEY_METHOD = "custom.declaration.order.method"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_KEY_CONSTRUCTOR = "custom.declaration.order.constructor"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_KEY_CLASS = "custom.declaration.order.class"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_KEY_INTERFACE = "custom.declaration.order.interface"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_KEY_ENUM = "custom.declaration.order.enum"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_KEY_INVALID_SETTER = "custom.declaration.order.invalid.setter"; private static final String INNER_ENUM_MACRO = "InnerEnum"; private static final String INNER_INTERFACE_MACRO = "InnerInterface"; private static final String INNER_CLASS_MACRO = "InnerClass"; private static final String CTOR_MACRO = "Ctor"; private static final String METHOD_MACRO = "Method"; private static final String ANNON_CLASS_FIELD_MACRO = "DeclareAnnonClassField"; private static final String FIELD_MACRO = "Field"; private static final String GETTER_SETTER_MACRO = "GetterSetter"; private static final String MAIN_METHOD_MACRO = "MainMethod"; private static final String BOOLEAN_GETTER_PREFIX = "is"; private static final String GETTER_PREFIX = "get"; private static final String SETTER_PREFIX = "set"; /** Default format for custom declaration check. */ private static final String DEFAULT_DECLARATION = "Field(.*public .*) " + "### Field(.*protected .*) ### Field(.*private .*) ### CTOR(.*) ### " + "MainMethod(.*) ### GetterSetter(.*) ### Method(.*) ### InnerClass(.*) " + "### InnerInterface(.*) ### InnerEnum(.*)"; /** * Compares line numbers. */ private static final Comparator<DetailAST> AST_LINE_COMPARATOR = new Comparator<DetailAST>() { @Override public int compare(DetailAST aObj1, DetailAST aObj2) { return aObj1.getLineNo() - aObj2.getLineNo(); } }; /** List of order declaration customizing by user. */ private final List<FormatMatcher> customOrderDeclaration = new ArrayList<>(); /** Save compile flags for further usage. */ private int compileFlags; /** Allow check inner classes. */ private boolean checkInnerClasses; /** * Allows to check getters and setters. */ private boolean checkGettersSetters; /** * Prefix of class fields. */ private String fieldPrefix = ""; /** * Stack of GetterSetterContainer objects to keep all getters and all setters * of certain class. */ private final Deque<ClassDetail> classDetails = new LinkedList<>(); /** Constructor to set default format. */ public CustomDeclarationOrderCheck() { setCustomDeclarationOrder(DEFAULT_DECLARATION); } /** * Set custom order declaration from string with user rules. * * @param inputOrderDeclaration The string line with the user custom * declaration. */ public final void setCustomDeclarationOrder(final String inputOrderDeclaration) { customOrderDeclaration.clear(); for (String currentState : inputOrderDeclaration.split("\\s*###\\s*")) { try { customOrderDeclaration .add(parseInputDeclarationRule(currentState)); } catch (StringIndexOutOfBoundsException exp) { //if the structure of the input rule isn't correct throw new IllegalArgumentException("Unable to parse input rule: " + currentState, exp); } } } /** * Set prefix of class fields. * @param fieldPrefix string */ public void setFieldPrefix(String fieldPrefix) { this.fieldPrefix = fieldPrefix; } /** * Set whether or not the match is case sensitive. * * @param caseSensitive true if the match is case sensitive. */ public void setCaseSensitive(final boolean caseSensitive) { // 0 - case sensitive flag if (caseSensitive) { compileFlags = 0; } else { compileFlags = Pattern.CASE_INSENSITIVE; } for (FormatMatcher currentRule : customOrderDeclaration) { currentRule.setCompileFlags(compileFlags); } } @Override public int[] getDefaultTokens() { final int size = customOrderDeclaration.size(); final int[] tokenTypes = new int[size + 1]; for (int i = 0; i < size; i++) { final FormatMatcher currentRule = customOrderDeclaration.get(i); tokenTypes[i] = currentRule.getClassMember(); if (currentRule.hasRule(INNER_CLASS_MACRO)) { checkInnerClasses = true; } else if (currentRule.hasRule(GETTER_SETTER_MACRO)) { checkGettersSetters = true; } } tokenTypes[size] = TokenTypes.CLASS_DEF; return tokenTypes; } @Override public void visitToken(DetailAST ast) { switch (ast.getType()) { case TokenTypes.CLASS_DEF: if (!isClassDefInMethodDef(ast)) { if (checkInnerClasses && !classDetails.isEmpty()) { final int position = getPositionInOrderDeclaration(ast); if (position != -1) { if (isWrongPosition(position)) { logWrongOrderedElement(ast, position); } else { classDetails.peek().setCurrentPosition(position); } } } classDetails.push(new ClassDetail()); } break; default: final DetailAST objBlockAst = ast.getParent(); if (objBlockAst != null && objBlockAst.getType() == TokenTypes.OBJBLOCK) { final DetailAST classDefAst = objBlockAst.getParent(); if (classDefAst.getType() == TokenTypes.CLASS_DEF && !isClassDefInMethodDef(classDefAst)) { if (checkGettersSetters) { collectGetterSetter(ast); } final int position = getPositionInOrderDeclaration(ast); if (position != -1) { if (isWrongPosition(position)) { logWrongOrderedElement(ast, position); } else { classDetails.peek().setCurrentPosition(position); } } } } } } @Override public void leaveToken(DetailAST ast) { if (ast.getType() == TokenTypes.CLASS_DEF && !isClassDefInMethodDef(ast)) { final ClassDetail classDetail = classDetails.pop(); if (checkGettersSetters) { final Map<DetailAST, DetailAST> gettersSetters = classDetail.getWrongOrderedGettersSetters(); logWrongOrderedSetters(gettersSetters); } } } /** * Parse input current declaration rule and create new instance of * FormatMather with matcher * * @param currentState input string with MemberDefinition and RegExp. * @return new FormatMatcher with parsed and compile rule */ private FormatMatcher parseInputDeclarationRule(final String currentState) { // parse mClassMember final String macro = currentState.substring(0, currentState.indexOf('(')).trim(); final int classMember = convertMacroToTokenType(macro); if (classMember == -1) { // if Class Member has been specified wrong throw new IllegalArgumentException("Unable to parse " + macro); } // parse regExp String regExp = currentState.substring( currentState.indexOf('(') + 1, currentState.lastIndexOf(')')); if (regExp.isEmpty()) { // package level regExp = "package"; } final FormatMatcher matcher = new FormatMatcher(currentState, classMember); matcher.updateRegexp(regExp, compileFlags); return matcher; } /** * Finds correspondence between the reduced name of class member of and * its complete naming in system. * * @param inputMemberName a string name which must be normalize. * @return correct name of member or initial string if no matches was * found. */ private static int convertMacroToTokenType( String inputMemberName) { int result = -1; if (FIELD_MACRO.equalsIgnoreCase(inputMemberName) || ANNON_CLASS_FIELD_MACRO.equalsIgnoreCase(inputMemberName)) { result = TokenTypes.VARIABLE_DEF; } else if (GETTER_SETTER_MACRO.equalsIgnoreCase(inputMemberName) || METHOD_MACRO.equalsIgnoreCase(inputMemberName) || MAIN_METHOD_MACRO.equalsIgnoreCase(inputMemberName)) { result = TokenTypes.METHOD_DEF; } else if (CTOR_MACRO.equalsIgnoreCase(inputMemberName)) { result = TokenTypes.CTOR_DEF; } else if (INNER_CLASS_MACRO.equalsIgnoreCase(inputMemberName)) { result = TokenTypes.CLASS_DEF; } else if (INNER_INTERFACE_MACRO.equalsIgnoreCase(inputMemberName)) { result = TokenTypes.INTERFACE_DEF; } else if (INNER_ENUM_MACRO.equalsIgnoreCase(inputMemberName)) { result = TokenTypes.ENUM_DEF; } return result; } /** * Verify that class definition is in method definition. * @param classDef * DetailAST of CLASS_DEF. * @return true if class definition is in method definition. */ private static boolean isClassDefInMethodDef(DetailAST classDef) { boolean result = false; DetailAST currentParentAst = classDef.getParent(); while (currentParentAst != null) { if (currentParentAst.getType() == TokenTypes.METHOD_DEF) { result = true; break; } currentParentAst = currentParentAst.getParent(); } return result; } /** * Logs wrong ordered element. * @param ast DetailAST of any class element. * @param position Position in the custom order declaration. */ private void logWrongOrderedElement(final DetailAST ast, final int position) { String token = null; switch (ast.getType()) { case TokenTypes.VARIABLE_DEF: token = MSG_KEY_FIELD; break; case TokenTypes.METHOD_DEF: token = MSG_KEY_METHOD; break; case TokenTypes.CTOR_DEF: token = MSG_KEY_CONSTRUCTOR; break; case TokenTypes.CLASS_DEF: token = MSG_KEY_CLASS; break; case TokenTypes.INTERFACE_DEF: token = MSG_KEY_INTERFACE; break; case TokenTypes.ENUM_DEF: token = MSG_KEY_ENUM; break; default: com.github.sevntu.checkstyle.Utils.reportInvalidToken(ast.getType()); break; } final int expectedPosition = classDetails.peek().getCurrentPosition(); log(ast, token, customOrderDeclaration.get(position).getRule(), customOrderDeclaration.get(expectedPosition).getRule()); } /** * Check that position is wrong in custom declaration order. * @param position position of class member. * @return true if position is wrong. */ private boolean isWrongPosition(final int position) { boolean result = false; final ClassDetail classDetail = classDetails.peek(); final Integer classCurrentPosition = classDetail.getCurrentPosition(); if (classCurrentPosition > position) { result = true; } return result; } /** * Log wrong ordered setters. * @param gettersSetters map that has getter as key and setter as value. */ private void logWrongOrderedSetters(Map<DetailAST, DetailAST> gettersSetters) { for (Entry<DetailAST, DetailAST> entry: gettersSetters.entrySet()) { final DetailAST setterAst = entry.getKey(); final DetailAST getterAst = entry.getValue(); log(setterAst.getLineNo(), MSG_KEY_INVALID_SETTER, getIdentifier(setterAst), getIdentifier(getterAst)); } } /** * If method definition is getter or setter, * then adds this method to collection. * @param methodDefAst DetailAST of method definition. */ private void collectGetterSetter(DetailAST methodDefAst) { if (methodDefAst.getType() == TokenTypes.METHOD_DEF) { final String methodName = getIdentifier(methodDefAst); if (isGetterName(methodName)) { if (isGetterCorrect(methodDefAst, GETTER_PREFIX)) { classDetails.peek().addGetter(methodDefAst); } } else if (isBooleanGetterName(methodName)) { if (isGetterCorrect(methodDefAst, BOOLEAN_GETTER_PREFIX)) { classDetails.peek().addGetter(methodDefAst); } } else if (isSetterName(methodName) && isSetterCorrect(methodDefAst, SETTER_PREFIX)) { classDetails.peek().addSetter(methodDefAst); } } } /** * Search in existing custom declaration order current aAST state. It's * necessary for getting order of declarations. * * @param ast current DetailAST state. * @return position in the list of the sequence declaration if * correspondence has been found. Else -1. */ private int getPositionInOrderDeclaration(final DetailAST ast) { int result = -1; final String modifiers = getCombinedModifiersList(ast); for (int index = 0; result != 1 && index < customOrderDeclaration.size(); index++) { final FormatMatcher currentRule = customOrderDeclaration.get(index); if (currentRule.getClassMember() == ast.getType() && currentRule.getRegexp().matcher(modifiers).find()) { if (currentRule.hasRule(ANNON_CLASS_FIELD_MACRO) || currentRule.hasRule(GETTER_SETTER_MACRO) || currentRule.hasRule(MAIN_METHOD_MACRO)) { final String methodName = getIdentifier(ast); final ClassDetail classDetail = classDetails.peek(); if (isAnonymousClassField(ast) || classDetail.containsGetter(methodName) || classDetail.containsSetter(methodName) || isMainMethod(ast)) { result = index; } } else { // if more than one rule matches current AST node, then keep first one if (result == -1) { result = index; } } } } return result; } /** * Verify that there is anonymous class in variable definition and this * variable is a field. * @param varDefinitionAst * DetailAST of variable definition. * @return true if there is anonymous class in variable definition and this * variable is a field. */ private static boolean isAnonymousClassField(DetailAST varDefinitionAst) { boolean result = false; // ClassDef -> ObjBlock -> VarDef final int parentType = varDefinitionAst.getParent().getParent().getType(); if (parentType == TokenTypes.CLASS_DEF) { final DetailAST assignAst = varDefinitionAst .findFirstToken(TokenTypes.ASSIGN); if (assignAst != null) { final DetailAST expressionToAssignAst = assignAst .findFirstToken(TokenTypes.EXPR); result = expressionToAssignAst != null && isAnonymousClass(expressionToAssignAst); } } return result; } /** * Verify that method name starts with getter prefix (get). * @param methodName method name * @return true if method name starts with getter prefix. */ private static boolean isGetterName(String methodName) { return methodName.startsWith(GETTER_PREFIX); } /** * Verify that method name starts with boolean getter prefix (is). * @param methodName method name * @return true if method name starts with boolean getter prefix. */ private static boolean isBooleanGetterName(String methodName) { return methodName.startsWith(BOOLEAN_GETTER_PREFIX); } /** * Verify that method name starts with setter prefix (set). * @param methodName method name * @return true if method name starts with setter prefix. */ private static boolean isSetterName(String methodName) { return methodName.startsWith(SETTER_PREFIX); } /** * Returns true when getter is correct. Correct getter is method that has no parameters, * returns class field and has name 'get<i>FieldName</i>'. * @param methodDef * - DetailAST contains method definition. * @param methodPrefix * Prefix for method (get, set, is). * @return true when getter is correct. */ private boolean isGetterCorrect(DetailAST methodDef, String methodPrefix) { boolean result = false; final String methodName = getIdentifier(methodDef); final String methodNameWithoutPrefix = getNameWithoutPrefix(methodName, methodPrefix); final DetailAST parameters = methodDef.findFirstToken(TokenTypes.PARAMETERS); // no parameters if (parameters.getChildCount() == 0) { final DetailAST statementsAst = methodDef.findFirstToken(TokenTypes.SLIST); if (statementsAst != null) { final DetailAST returnStatementAst = statementsAst .findFirstToken(TokenTypes.LITERAL_RETURN); if (returnStatementAst != null) { final DetailAST exprAst = returnStatementAst.getFirstChild(); final String returnedFieldName = getNameOfGetterField(exprAst); if (returnedFieldName != null && !localVariableHidesField(statementsAst, returnedFieldName) && verifyFieldAndMethodName(returnedFieldName, methodNameWithoutPrefix)) { result = true; } } } } return result; } /** * Checks if a local variable hides a field. * @param slist The token to examine. * @param fieldName The name of the field. * @return true if the local variable is hidden from a field. */ private static boolean localVariableHidesField(DetailAST slist, String fieldName) { boolean result = false; DetailAST currNode = slist.getFirstChild(); while (currNode != null) { if (currNode.getType() == TokenTypes.VARIABLE_DEF && fieldName.equals(getIdentifier(currNode))) { result = true; break; } currNode = currNode.getNextSibling(); } return result; } /** * Returns true when setter is correct. Correct setter is method that has one parameter, * assigns this parameter to class field and has name 'set<i>FieldName</i>'. * @param methodDefAst * - DetailAST contains method definition. * @param methodPrefix * Prefix for method (get, set, is). * @return true when setter is correct. */ private boolean isSetterCorrect(DetailAST methodDefAst, String methodPrefix) { boolean result = false; final String methodName = getIdentifier(methodDefAst); final String setterFieldName = fieldPrefix + getNameWithoutPrefix(methodName, methodPrefix); final DetailAST methodTypeAst = methodDefAst.findFirstToken(TokenTypes.TYPE); if (methodTypeAst.branchContains(TokenTypes.LITERAL_VOID)) { final DetailAST statementsAst = methodDefAst.findFirstToken(TokenTypes.SLIST); result = statementsAst != null && !localVariableHidesField(statementsAst, setterFieldName) && isFieldUpdate(statementsAst, setterFieldName); } return result; } /** * Verify that expression is anonymous class. * @param expressionAst * DetailAST of expression. * @return true if expression is anonymous class. */ private static boolean isAnonymousClass(DetailAST expressionAst) { boolean result = false; final DetailAST literalNewAst = expressionAst .findFirstToken(TokenTypes.LITERAL_NEW); if (literalNewAst != null) { final DetailAST objBlockAst = literalNewAst.findFirstToken(TokenTypes.OBJBLOCK); result = objBlockAst != null; } return result; } /** * Use for concatenation modifiers, annotations, type and * name of member in single line. <br> * Contains TokenTypes parameters for entry in child. * * @param ast current DetailAST state. * @return the unit annotations and modifiers and list. */ private static String getCombinedModifiersList(final DetailAST ast) { final StringBuilder modifiers = new StringBuilder(); DetailAST astNode = ast.findFirstToken(TokenTypes.MODIFIERS); if (astNode.getFirstChild() == null) { //if we met package level modifier modifiers.append("package "); } while (astNode.getType() != TokenTypes.IDENT) { if (astNode != null && astNode.getFirstChild() != null) { modifiers.append(getModifiersAsText(astNode.getFirstChild())); modifiers.append(" "); } astNode = astNode.getNextSibling(); } // add IDENT(name) modifiers.append(astNode.getText()); return modifiers.toString(); } /** * Get text representation of MODIFIERS node. * * @param ast current DetailAST node. * @return text representation of MODIFIERS node. */ private static String getModifiersAsText(final DetailAST ast) { DetailAST astNode = ast; String separator = ""; final StringBuffer modifiers = new StringBuffer(); if (astNode.getParent().getType() == TokenTypes.MODIFIERS) { // add separator between access modifiers and annotations separator = " "; } while (astNode != null) { if (astNode.getFirstChild() != null) { modifiers.append(getModifiersAsText(astNode.getFirstChild())); } else { if (astNode.getType() == TokenTypes.RBRACK) { //if array modifiers.append("["); } modifiers.append(astNode.getText()); } modifiers.append(separator); astNode = astNode.getNextSibling(); } return modifiers.toString().trim(); } /** * Get name without prefix. * @param name name * @param prefix prefix * @return name without prefix or null if name does not have such prefix. */ private static String getNameWithoutPrefix(String name, String prefix) { String result = null; if (name.startsWith(prefix)) { result = name.substring(prefix.length()); result = Introspector.decapitalize(result); } return result; } /** * Get identifier of AST. These can be names of types, subpackages, * fields, methods, parameters, and local variables. * @param ast * DetailAST instance * @return identifier of AST, null if AST does not have name. */ private static String getIdentifier(final DetailAST ast) { final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT); if (ident != null) { return ident.getText(); } return null; } /** * Verify that exists updating of a field. * @param statementsAst DetailAST of statements (SLIST). * @param fieldName name of target field. * @return true if there is updating of aFieldName in aStatementsAst. */ private static boolean isFieldUpdate(DetailAST statementsAst, String fieldName) { boolean result = false; DetailAST currentStatement = statementsAst.getFirstChild(); while (currentStatement != null && currentStatement != statementsAst) { String nameOfSetterField = null; if (currentStatement.getType() == TokenTypes.ASSIGN) { nameOfSetterField = getNameOfAssignedField(currentStatement); } else if (currentStatement.getType() == TokenTypes.METHOD_CALL) { nameOfSetterField = getNameOfSuperClassUpdatedField(currentStatement); } if (fieldName.equalsIgnoreCase(nameOfSetterField)) { result = true; break; } DetailAST nextStatement = currentStatement.getFirstChild(); while (currentStatement != null && nextStatement == null) { nextStatement = currentStatement.getNextSibling(); if (nextStatement == null) { currentStatement = currentStatement.getParent(); } } currentStatement = nextStatement; } return result; } /** * <p> * Return name of the field, that was assigned in current setter. * </p> * @param assignAst * - DetailAST contains ASSIGN from EXPR of the setter. * @return name of field, that use in setter. */ private static String getNameOfAssignedField(DetailAST assignAst) { String nameOfSettingField = null; if (assignAst.getChildCount() > 0 && (assignAst.getLastChild().getType() == TokenTypes.IDENT || assignAst.getLastChild().getType() == TokenTypes.METHOD_CALL)) { final DetailAST methodCallDot = assignAst.getFirstChild(); if (methodCallDot.getChildCount() == 2 && "this".equals(methodCallDot.getFirstChild().getText())) { nameOfSettingField = methodCallDot.getLastChild().getText(); } } return nameOfSettingField; } /** * <p> * Return name of the field of a super class, that was assigned in setter. * </p> * @param methodCallAst * - DetailAST contains METHOD_CALL from EXPR of the setter. * @return name of field, that used in setter. */ private static String getNameOfSuperClassUpdatedField(DetailAST methodCallAst) { String nameOfSettingField = null; final DetailAST methodCallDot = methodCallAst.getFirstChild(); if (methodCallDot.getChildCount() == 2 && "super".equals(methodCallDot.getFirstChild().getText())) { nameOfSettingField = getFieldName(methodCallDot); } return nameOfSettingField; } /** * <p> * Gets name of the field, that was used in calling setter from a super class * </p> * @param methodCallDotAst The token to examine. * @return * name of field in method parameter. */ private static String getFieldName(final DetailAST methodCallDotAst) { String nameOfSettingField = null; final DetailAST parameterOfSetterMethod = methodCallDotAst.getNextSibling().getFirstChild(); if (parameterOfSetterMethod != null) { nameOfSettingField = parameterOfSetterMethod.getFirstChild().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) { return (fieldPrefix + methodName).equalsIgnoreCase(fieldName); } /** * <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 getNameOfGetterField(DetailAST expr) { String nameOfGetterField = null; if (expr.getChildCount() == 1) { final DetailAST exprFirstChild = expr.getFirstChild(); if (exprFirstChild.getType() == TokenTypes.IDENT) { nameOfGetterField = exprFirstChild.getText(); } else if (exprFirstChild.getType() == TokenTypes.DOT && exprFirstChild.getChildCount() == 2 && exprFirstChild.getFirstChild().getType() == TokenTypes.LITERAL_THIS && exprFirstChild.getLastChild().getType() == TokenTypes.IDENT) { nameOfGetterField = exprFirstChild.getLastChild().getText(); } } return nameOfGetterField; } /** * Verifies that the given DetailAST is a main method. * @param methodAST * DetailAST instance. * @return true if aMethodAST is a main method, false otherwise. */ private static boolean isMainMethod(final DetailAST methodAST) { boolean result = true; final String methodName = getIdentifier(methodAST); if ("main".equals(methodName)) { result = isVoidType(methodAST) && isMainMethodModifiers(methodAST) && isMainMethodParameters(methodAST); } else { result = false; } return result; } /** * Verifies that given AST has appropriate modifiers for main method. * @param methodAST * DetailAST instance. * @return true if aMethodAST has (public & static & !abstract) modifiers, * false otherwise. */ private static boolean isMainMethodModifiers(final DetailAST methodAST) { boolean result = false; if (hasChildToken(methodAST, TokenTypes.MODIFIERS)) { final DetailAST modifiers = methodAST.findFirstToken(TokenTypes.MODIFIERS); result = hasChildToken(modifiers, TokenTypes.LITERAL_PUBLIC) && hasChildToken(modifiers, TokenTypes.LITERAL_STATIC); } return result; } /** * Verifies that given AST has type and this type is void. * @param methodAST * DetailAST instance. * @return true if AST's type void, false otherwise. */ private static boolean isVoidType(final DetailAST methodAST) { boolean result = true; DetailAST methodTypeAST = null; if (hasChildToken(methodAST, TokenTypes.TYPE)) { methodTypeAST = methodAST.findFirstToken(TokenTypes.TYPE); result = hasChildToken(methodTypeAST, TokenTypes.LITERAL_VOID); } return result; } /** * Verifies that given AST has appropriate for main method parameters. * @param methodAST * instance of a method * @return true if parameters of aMethodAST are appropriate for main method, * false otherwise. */ private static boolean isMainMethodParameters(final DetailAST methodAST) { final DetailAST params = methodAST.findFirstToken(TokenTypes.PARAMETERS); return hasOnlyStringArrayParameter(params) || hasOnlyStringEllipsisParameter(params); } /** * Return true if AST of method parameters has String[] parameter child * token. * @param parametersAST * DetailAST of method parameters. * @return true if AST has String[] parameter child token, false otherwise. */ private static boolean hasOnlyStringArrayParameter(final DetailAST parametersAST) { boolean result = true; if (parametersAST.getChildCount(TokenTypes.PARAMETER_DEF) == 1) { final DetailAST parameterDefinitionAST = parametersAST.findFirstToken(TokenTypes.PARAMETER_DEF); final DetailAST parameterTypeAST = parameterDefinitionAST .findFirstToken(TokenTypes.TYPE); if (hasChildToken(parameterTypeAST, TokenTypes.ARRAY_DECLARATOR)) { final DetailAST arrayDeclaratorAST = parameterTypeAST .findFirstToken(TokenTypes.ARRAY_DECLARATOR); final String parameterName = getIdentifier(arrayDeclaratorAST); result = "String".equals(parameterName); } else { result = false; } } else { // there is none or multiple parameters result = false; } return result; } /** * Return true if AST of method parameters has String... parameter child * token. * @param parametersAST * DetailAST of method parameters. * @return true if aParametersAST has String... parameter child token, false * otherwise. */ private static boolean hasOnlyStringEllipsisParameter(final DetailAST parametersAST) { boolean result = true; if (parametersAST.getChildCount(TokenTypes.PARAMETER_DEF) == 1) { final DetailAST parameterDefinitionAST = parametersAST.findFirstToken(TokenTypes.PARAMETER_DEF); if (hasChildToken(parameterDefinitionAST, TokenTypes.ELLIPSIS)) { final DetailAST parameterTypeAST = parameterDefinitionAST.findFirstToken(TokenTypes.TYPE); final String parameterName = getIdentifier(parameterTypeAST); result = "String".equals(parameterName); } else { result = false; } } else { // there is none or multiple parameters result = false; } return result; } /** * Return true if aAST has token of aTokenType type. * @param ast * DetailAST instance. * @param tokenType * one of TokenTypes * @return true if aAST has token of given type, or false otherwise. */ private static boolean hasChildToken(DetailAST ast, int tokenType) { return ast.findFirstToken(tokenType) != null; } /** * Private class for members of class and their patterns. */ private static final class FormatMatcher { /** The regexp to match against. */ private Pattern regExp; /** The Member of Class. */ private final int classMember; /** The input full one rule with original names. */ private final String rule; /** The string format of the RegExp. */ private String format; /** * Creates a new <code>FormatMatcher</code> instance. * * @param inputRule input string with MemberDefinition and RegExp. * @param classMember the member of class */ private FormatMatcher(final String inputRule, final int classMember) { this.classMember = classMember; rule = inputRule; } /** * Getter for the regexp field. * @return the RegExp to match against */ public Pattern getRegexp() { return regExp; } /** Getter for the rule field. * @return the original immutable input rule */ public String getRule() { return rule; } /** * Getter for the class member field. * @return the Class Member */ public int getClassMember() { return classMember; } /** * Set the compile flags for the regular expression. * * @param compileFlags the compile flags to use. */ public void setCompileFlags(final int compileFlags) { updateRegexp(format, compileFlags); } /** * Updates the regular expression using the supplied format and compiler * flags. Will also update the member variables. * * @param newFormat the format of the regular expression. * @param compileFlags the compiler flags to use. */ private void updateRegexp(final String newFormat, final int compileFlags) { try { regExp = Pattern.compile(newFormat, compileFlags); this.format = newFormat; } catch (final PatternSyntaxException ex) { throw new IllegalArgumentException("unable to parse " + newFormat, ex); } } /** * Check that format matcher contains rule. * @param ruleCheck string * @return true if format matcher contains rule. */ public boolean hasRule(String ruleCheck) { return this.rule.indexOf(ruleCheck) > -1; } @Override public String toString() { return rule; } } /** * Class to keep current position and collect getters, setters. */ private static class ClassDetail { /** * Current position in custom order declaration. */ private int currentPosition; /** * List of getter ASTs. */ private final List<DetailAST> getters = new LinkedList<>(); /** * List of setter ASTs. */ private final List<DetailAST> setters = new LinkedList<>(); public int getCurrentPosition() { return currentPosition; } public void setCurrentPosition(int position) { currentPosition = position; } /** * Add getter. * @param getterAst DetailAST of getter. */ public void addGetter(DetailAST getterAst) { getters.add(getterAst); } /** * Add setter. * @param setterAst DetailAST of setter. */ public void addSetter(DetailAST setterAst) { setters.add(setterAst); } /** * Compare order of getters and setters. Order should be like "getX; setX; getY; setY; ...". * If it is wrong order, then wrong ordered setters and getters will be returned as map. * @return Map with setter AST as key and getter AST as value. */ public Map<DetailAST, DetailAST> getWrongOrderedGettersSetters() { final Map<DetailAST, DetailAST> result = new LinkedHashMap<>(); if (!getters.isEmpty() && !setters.isEmpty()) { // all getters and setters final List<DetailAST> allGettersSetters = new ArrayList<>(getters); allGettersSetters.addAll(setters); // sort by line numbers Collections.sort(allGettersSetters, AST_LINE_COMPARATOR); for (int i = 0; i < allGettersSetters.size(); i++) { result.putAll(getWrongOrderedGettersSetters(allGettersSetters, i)); } } return result; } /** * Compare order of getters and setters. Order should be like "getX; setX; getY; setY; ...". * If it is wrong order, then wrong ordered setters and getters will be returned as map. * @param allGettersSetters collection of all gettter and setters * @param index index from upper loo * @return Map with setter AST as key and getter AST as value. */ private static Map<DetailAST, DetailAST> getWrongOrderedGettersSetters( List<DetailAST> allGettersSetters, int index) { final DetailAST getterAst = allGettersSetters.get(index); final String getterName = getIdentifier(getterAst); String getterField = null; if (isGetterName(getterName)) { getterField = getNameWithoutPrefix(getIdentifier(getterAst), GETTER_PREFIX); } else if (isBooleanGetterName(getterName)) { getterField = getNameWithoutPrefix(getIdentifier(getterAst), BOOLEAN_GETTER_PREFIX); } final Map<DetailAST, DetailAST> result = new LinkedHashMap<>(); if (getterField != null) { // review rest of the list to find a proper setter for (int j = 0; j < allGettersSetters.size(); j++) { if (index != j) { // method is NOT getter final DetailAST setterAst = allGettersSetters.get(j); final String setterName = getIdentifier(setterAst); String setterField = null; if (isSetterName(setterName)) { setterField = getNameWithoutPrefix( getIdentifier(setterAst), SETTER_PREFIX); // if fields are same and setter is sibling with getter if (j != index + 1 && getterField.equals(setterField)) { result.put(setterAst, getterAst); break; } } } } } return result; } /** * Verify that specified method was saved as getter. * @param methodName name of method. * @return true if specified method was saved as getter. */ private boolean containsGetter(String methodName) { boolean result = false; for (DetailAST methodAst: getters) { final String name = getIdentifier(methodAst); if (name.equals(methodName)) { result = true; } } return result; } /** * Verify that specified method was saved as setter. * @param methodName name of method. * @return true if specified method was saved as setter. */ private boolean containsSetter(String methodName) { boolean result = false; for (DetailAST methodAst: setters) { final String name = getIdentifier(methodAst); if (name.equals(methodName)) { result = true; } } return result; } } }