com.github.sevntu.checkstyle.checks.coding.CustomDeclarationOrderCheck.java Source code

Java tutorial

Introduction

Here is the source code for com.github.sevntu.checkstyle.checks.coding.CustomDeclarationOrderCheck.java

Source

////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2011  Oliver Burn
//
// 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 org.apache.commons.beanutils.ConversionException;

import com.puppycrawl.tools.checkstyle.api.Check;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.api.Utils;

/**
 * <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>
 * </p>
 * To set class order use the following notation of the class members (case
 * insensitive):
 * <p>
 * <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>
 * </p>
 * RegExp can include:
 * <p>
 * <ol>
 * <li>Annotations</li>
 * <li>Modifiers(public, protected, private, abstract, static,
 * final)</li>
 * <li>Type</li>
 * <li>Name</li>
 * </ol>
 * </p>
 * ATTENTION!
 * <p>
 * Use separator <code>' ', '.', '\s'</code> between declaration in the RegExp.
 * </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&lt;String[]&gt; 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>
 * <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 Check {
    public static final String MSG_KEY_FIELD = "custom.declaration.order.field";

    public static final String MSG_KEY_METHOD = "custom.declaration.order.method";

    public static final String MSG_KEY_CONSTRUCTOR = "custom.declaration.order.constructor";

    public static final String MSG_KEY_CLASS = "custom.declaration.order.class";

    public static final String MSG_KEY_INTERFACE = "custom.declaration.order.interface";

    public static final String MSG_KEY_ENUM = "custom.declaration.order.enum";

    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> mCustomOrderDeclaration = new ArrayList<FormatMatcher>();

    /** save compile flags for further usage */
    private int mCompileFlags;

    /** allow check inner classes */
    private boolean mCheckInnerClasses;

    /**
     * Allows to check getters and setters.
     */
    private boolean mCheckGettersSetters;

    /**
     * Prefix of class fields.
     */
    private String mFieldPrefix = "";

    /**
     * Stack of GetterSetterContainer objects to keep all getters and all setters
     * of certain class.
     */
    private final Deque<ClassDetail> mClassDetails = new LinkedList<ClassDetail>();

    /** Constructor to set default format. */
    public CustomDeclarationOrderCheck() {
        setCustomDeclarationOrder(DEFAULT_DECLARATION);
    }

    /**
     * Set custom order declaration from string with user rules.
     *
     * @param aInputOrderDeclaration The string line with the user custom
     *            declaration.
     */
    public void setCustomDeclarationOrder(final String aInputOrderDeclaration) {
        mCustomOrderDeclaration.clear();
        for (String currentState : aInputOrderDeclaration.split("\\s*###\\s*")) {
            try {
                mCustomOrderDeclaration.add(parseInputDeclarationRule(currentState));
            } catch (StringIndexOutOfBoundsException exp) {
                //if the structure of the input rule isn't correct
                throw new RuntimeException("Unable to parse input rule: " + currentState, exp);
            }
        }
    }

    /**
     * Set prefix of class fields.
     * @param aFieldPrefix string
     */
    public void setFieldPrefix(String aFieldPrefix) {
        mFieldPrefix = aFieldPrefix;
    }

    /**
     * Set whether or not the match is case sensitive.
     *
     * @param aCaseSensitive true if the match is case sensitive.
     */
    public void setCaseSensitive(final boolean aCaseSensitive) {
        // 0 - case sensitive flag
        mCompileFlags = aCaseSensitive ? 0 : Pattern.CASE_INSENSITIVE;

        for (FormatMatcher currentRule : mCustomOrderDeclaration) {
            currentRule.setCompileFlags(mCompileFlags);
        }
    }

    @Override
    public int[] getDefaultTokens() {
        final int size = mCustomOrderDeclaration.size();
        final int[] tokenTypes = new int[size + 1];

        for (int i = 0; i < size; i++) {
            final FormatMatcher currentRule = mCustomOrderDeclaration.get(i);
            tokenTypes[i] = currentRule.getClassMember();

            if (currentRule.hasRule(INNER_CLASS_MACRO)) {
                mCheckInnerClasses = true;
            } else if (currentRule.hasRule(GETTER_SETTER_MACRO)) {
                mCheckGettersSetters = true;
            }
        }

        tokenTypes[size] = TokenTypes.CLASS_DEF;

        return tokenTypes;
    }

    @Override
    public void visitToken(DetailAST aAST) {
        switch (aAST.getType()) {

        case TokenTypes.CLASS_DEF:
            if (!isClassDefInMethodDef(aAST)) {
                if (mCheckInnerClasses && !mClassDetails.isEmpty()) {

                    final int position = getPositionInOrderDeclaration(aAST);

                    if (position != -1) {
                        if (isWrongPosition(position)) {
                            logWrongOrderedElement(aAST, position);
                        } else {
                            mClassDetails.peek().setCurrentPosition(position);
                        }
                    }
                }

                mClassDetails.push(new ClassDetail());
            }
            break;

        default:
            final DetailAST objBlockAst = aAST.getParent();
            if (objBlockAst != null && objBlockAst.getType() == TokenTypes.OBJBLOCK) {

                final DetailAST classDefAst = objBlockAst.getParent();

                if (classDefAst.getType() == TokenTypes.CLASS_DEF && !isClassDefInMethodDef(classDefAst)) {
                    if (mCheckGettersSetters) {
                        collectGetterSetter(aAST);
                    }

                    final int position = getPositionInOrderDeclaration(aAST);

                    if (position != -1) {
                        if (isWrongPosition(position)) {
                            logWrongOrderedElement(aAST, position);
                        } else {
                            mClassDetails.peek().setCurrentPosition(position);
                        }
                    }
                }
            }

        }
    }

    @Override
    public void leaveToken(DetailAST aAST) {
        if (aAST.getType() == TokenTypes.CLASS_DEF && !isClassDefInMethodDef(aAST)) {
            final ClassDetail classDetail = mClassDetails.pop();

            if (mCheckGettersSetters) {

                final Map<DetailAST, DetailAST> gettersSetters = classDetail.getWrongOrderedGettersSetters();

                logWrongOrderedSetters(gettersSetters);
            }
        }
    }

    /**
     * Parse input current declaration rule and create new instance of
     * FormatMather with matcher
     *
     * @param aCurrentState input string with MemberDefinition and RegExp.
     * @return new FormatMatcher with parsed and compile rule
     */
    private FormatMatcher parseInputDeclarationRule(final String aCurrentState) {
        // parse mClassMember
        final String macro = aCurrentState.substring(0, aCurrentState.indexOf('(')).trim();
        final int classMember = convertMacroToTokenType(macro);
        if (classMember == -1) {
            // if Class Member has been specified wrong
            throw new ConversionException("Unable to parse " + macro);
        }

        // parse regExp
        String regExp = aCurrentState.substring(aCurrentState.indexOf('(') + 1, aCurrentState.lastIndexOf(')'));
        if (regExp.isEmpty()) {
            regExp = "package"; // package level
        }

        final FormatMatcher matcher = new FormatMatcher(aCurrentState, classMember);
        matcher.updateRegexp(regExp, mCompileFlags);

        return matcher;
    }

    /**
     * Finds correspondence between the reduced name of class member of and
     * its complete naming in system.
     *
     * @param aInputMemberName 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 aInputMemberName) {
        int result = -1;
        if (FIELD_MACRO.equalsIgnoreCase(aInputMemberName)
                || ANNON_CLASS_FIELD_MACRO.equalsIgnoreCase(aInputMemberName)) {
            result = TokenTypes.VARIABLE_DEF;
        } else if (GETTER_SETTER_MACRO.equalsIgnoreCase(aInputMemberName)
                || METHOD_MACRO.equalsIgnoreCase(aInputMemberName)
                || MAIN_METHOD_MACRO.equalsIgnoreCase(aInputMemberName)) {
            result = TokenTypes.METHOD_DEF;
        } else if (CTOR_MACRO.equalsIgnoreCase(aInputMemberName)) {
            result = TokenTypes.CTOR_DEF;
        } else if (INNER_CLASS_MACRO.equalsIgnoreCase(aInputMemberName)) {
            result = TokenTypes.CLASS_DEF;
        } else if (INNER_INTERFACE_MACRO.equalsIgnoreCase(aInputMemberName)) {
            result = TokenTypes.INTERFACE_DEF;
        } else if (INNER_ENUM_MACRO.equalsIgnoreCase(aInputMemberName)) {
            result = TokenTypes.ENUM_DEF;
        }
        return result;
    }

    /**
     * Verify that class definition is in method definition.
     * @param aClassDef
     *        DetailAST of CLASS_DEF.
     * @return true if class definition is in method definition.
     */
    private static boolean isClassDefInMethodDef(DetailAST aClassDef) {
        boolean result = false;
        DetailAST currentParentAst = aClassDef.getParent();
        while (currentParentAst != null) {
            if (currentParentAst.getType() == TokenTypes.METHOD_DEF) {
                result = true;
                break;
            }
            currentParentAst = currentParentAst.getParent();
        }
        return result;
    }

    /**
     * Logs wrong ordered element.
     * @param aAST DetailAST of any class element.
     */
    private void logWrongOrderedElement(final DetailAST aAST, final int aPosition) {
        String token;
        switch (aAST.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:
            token = "Unknown element: " + aAST.getType();
        }

        final int expectedPosition = mClassDetails.peek().getCurrentPosition();
        log(aAST, token, mCustomOrderDeclaration.get(aPosition).getRule(),
                mCustomOrderDeclaration.get(expectedPosition).getRule());
    }

    /**
     * Check that position is wrong in custom declaration order.
     * @param aPosition position of class member.
     * @return true if position is wrong.
     */
    private boolean isWrongPosition(final int aPosition) {
        boolean result = false;
        final ClassDetail classDetail = mClassDetails.peek();
        final Integer classCurrentPosition = classDetail.getCurrentPosition();
        if (classCurrentPosition > aPosition) {
            result = true;
        }
        return result;
    }

    /**
     * Log wrong ordered setters.
     * @param aGettersSetters map that has getter as key and setter as value.
     */
    private void logWrongOrderedSetters(Map<DetailAST, DetailAST> aGettersSetters) {
        for (Entry<DetailAST, DetailAST> entry : aGettersSetters.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 aMethodDefAst DetailAST of method definition.
     */
    private void collectGetterSetter(DetailAST aMethodDefAst) {
        if (aMethodDefAst.getType() == TokenTypes.METHOD_DEF) {
            final String methodName = getIdentifier(aMethodDefAst);
            if (isGetterName(methodName)) {
                if (isGetterCorrect(aMethodDefAst, GETTER_PREFIX)) {
                    mClassDetails.peek().addGetter(aMethodDefAst);
                }
            } else if (isBooleanGetterName(methodName)) {
                if (isGetterCorrect(aMethodDefAst, BOOLEAN_GETTER_PREFIX)) {
                    mClassDetails.peek().addGetter(aMethodDefAst);
                }
            } else if (isSetterName(methodName) && isSetterCorrect(aMethodDefAst, SETTER_PREFIX)) {
                mClassDetails.peek().addSetter(aMethodDefAst);
            }
        }
    }

    /**
     * Search in existing custom declaration order current aAST state. It's
     * necessary for getting order of declarations.
     *
     * @param aAST current DetailAST state.
     * @return position in the list of the sequence declaration if
     *         correspondence has been found. Else -1.
     */
    private int getPositionInOrderDeclaration(final DetailAST aAST) {
        int result = -1;
        final String modifiers = getCombinedModifiersList(aAST);
        for (int index = 0; index < mCustomOrderDeclaration.size(); index++) {
            final FormatMatcher currentRule = mCustomOrderDeclaration.get(index);
            if (currentRule.getClassMember() == aAST.getType()
                    && currentRule.getRegexp().matcher(modifiers).find()) {
                if (currentRule.hasRule(ANNON_CLASS_FIELD_MACRO)) {
                    if (isAnonymousClassField(aAST)) {
                        result = index;
                        break;
                    }
                } else if (currentRule.hasRule(GETTER_SETTER_MACRO)) {
                    final String methodName = getIdentifier(aAST);
                    final ClassDetail classDetail = mClassDetails.peek();
                    if (classDetail.containsGetter(methodName) || classDetail.containsSetter(methodName)) {
                        result = index;
                        break;
                    }
                } else if (currentRule.hasRule(MAIN_METHOD_MACRO)) {
                    if (isMainMethod(aAST)) {
                        result = index;
                        break;
                    }
                } else {
                    // if more than one rule matches current AST node, then keep first one
                    result = (result == -1) ? index : result;
                    if (aAST.getType() == TokenTypes.METHOD_DEF || aAST.getType() == TokenTypes.VARIABLE_DEF) {
                        // continue to find more specific rule
                        continue;
                    } else {
                        break;
                    }
                }
            }
        }
        return result;
    }

    /**
     * Verify that there is anonymous class in variable definition and this
     * variable is a field.
     * @param aVarDefinitionAst
     *        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 aVarDefinitionAst) {
        boolean result = false;
        // ClassDef -> ObjBlock -> VarDef
        final int parentType = aVarDefinitionAst.getParent().getParent().getType();
        if (parentType == TokenTypes.CLASS_DEF) {
            final DetailAST assignAst = aVarDefinitionAst.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 aMethodName method name
     * @return true if method name starts with getter prefix.
     */
    private static boolean isGetterName(String aMethodName) {
        return aMethodName.startsWith(GETTER_PREFIX);
    }

    /**
     * Verify that method name starts with boolean getter prefix (is).
     * @param aMethodName method name
     * @return true if method name starts with boolean getter prefix.
     */
    private static boolean isBooleanGetterName(String aMethodName) {
        return aMethodName.startsWith(BOOLEAN_GETTER_PREFIX);
    }

    /**
     * Verify that method name starts with setter prefix (set).
     * @param aMethodName method name
     * @return true if method name starts with setter prefix.
     */
    private static boolean isSetterName(String aMethodName) {
        return aMethodName.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 aMethodDef
     *        - DetailAST contains method definition.
     * @param aMethodPrefix
     *          Prefix for method (get, set, is).
     * @return true when getter is correct.
     */
    private boolean isGetterCorrect(DetailAST aMethodDef, String aMethodPrefix) {
        boolean result = false;

        final String methodName = getIdentifier(aMethodDef);
        final String methodNameWithoutPrefix = getNameWithoutPrefix(methodName, aMethodPrefix);

        final DetailAST parameters = aMethodDef.findFirstToken(TokenTypes.PARAMETERS);

        // no parameters
        if (parameters.getChildCount() == 0) {

            final DetailAST statementsAst = aMethodDef.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;
    }

    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 aMethodDefAst
     *        - DetailAST contains method definition.
     * @param aMethodPrefix
     *          Prefix for method (get, set, is).
     * @return true when setter is correct.
     */
    private boolean isSetterCorrect(DetailAST aMethodDefAst, String aMethodPrefix) {
        boolean result = false;

        final String methodName = getIdentifier(aMethodDefAst);
        final String setterFieldName = mFieldPrefix + getNameWithoutPrefix(methodName, aMethodPrefix);

        final DetailAST methodTypeAst = aMethodDefAst.findFirstToken(TokenTypes.TYPE);

        if (methodTypeAst.branchContains(TokenTypes.LITERAL_VOID)) {

            final DetailAST statementsAst = aMethodDefAst.findFirstToken(TokenTypes.SLIST);

            result = statementsAst != null && !localVariableHidesField(statementsAst, setterFieldName)
                    && isFieldUpdate(statementsAst, setterFieldName);
        }
        return result;
    }

    /**
     * Verify that expression is anonymous class.
     * @param aExpressionAst
     *        DetailAST of expression.
     * @return true if expression is anonymous class.
     */
    private static boolean isAnonymousClass(DetailAST aExpressionAst) {
        boolean result = false;
        final DetailAST literalNewAst = aExpressionAst.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. </br>
     *
     * @param aAST current DetailAST state.
     * @return the unit annotations and modifiers and list.
     */
    private static String getCombinedModifiersList(final DetailAST aAST) {
        final StringBuffer modifiers = new StringBuffer();
        DetailAST ast = aAST.findFirstToken(TokenTypes.MODIFIERS);
        if (ast.getFirstChild() == null) {
            //if we met package level modifier
            modifiers.append("package ");
        }

        while (ast.getType() != TokenTypes.IDENT) {
            if (ast != null && ast.getFirstChild() != null) {
                modifiers.append(getModifiersAsText(ast.getFirstChild()));
                modifiers.append(" ");
            }
            ast = ast.getNextSibling();
        }
        // add IDENT(name)
        modifiers.append(ast.getText());

        return modifiers.toString();
    }

    /**
     * Get text representation of MODIFIERS node.
     *
     * @param aAST current DetailAST node.
     * @return text representation of MODIFIERS node.
     */
    private static String getModifiersAsText(final DetailAST aAST) {
        DetailAST ast = aAST;
        String separator = "";
        final StringBuffer modifiers = new StringBuffer();

        if (ast.getParent().getType() == TokenTypes.MODIFIERS) {
            // add separator between access modifiers and annotations
            separator = " ";
        }
        while (ast != null) {
            if (ast.getFirstChild() != null) {
                modifiers.append(getModifiersAsText(ast.getFirstChild()));
            } else {
                if (ast.getType() == TokenTypes.RBRACK) {
                    //if array
                    modifiers.append("[");
                }
                modifiers.append(ast.getText());
            }
            modifiers.append(separator);
            ast = ast.getNextSibling();
        }
        return modifiers.toString().trim();
    }

    /**
     * Get name without prefix.
     * @param aName name
     * @param aPrefix prefix
     * @return name without prefix or null if name does not have such prefix.
     */
    private static String getNameWithoutPrefix(String aName, String aPrefix) {
        String result = null;
        if (aName.startsWith(aPrefix)) {
            result = aName.substring(aPrefix.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 aAST
     *        DetailAST instance
     * @return identifier of AST, null if AST does not have name.
     */
    private static String getIdentifier(final DetailAST aAST) {
        final DetailAST ident = aAST.findFirstToken(TokenTypes.IDENT);
        if (ident != null) {
            return ident.getText();
        }
        return null;
    }

    /**
     * Verify that exists updating of a field.
     * @param aStatementsAst DetailAST of statements (SLIST).
     * @param aFieldName name of target field.
     * @return true if there is updating of aFieldName in aStatementsAst.
     */
    private static boolean isFieldUpdate(DetailAST aStatementsAst, String aFieldName) {
        boolean result = false;
        DetailAST currentStatement = aStatementsAst.getFirstChild();

        while (currentStatement != null && currentStatement != aStatementsAst) {

            String nameOfSetterField = null;
            if (currentStatement.getType() == TokenTypes.ASSIGN) {
                nameOfSetterField = getNameOfAssignedField(currentStatement);
            } else if (currentStatement.getType() == TokenTypes.METHOD_CALL) {
                nameOfSetterField = getNameOfSuperClassUpdatedField(currentStatement);
            }

            if (aFieldName.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
     * @return
     *      name of field in method parameter.
     */
    private static String getFieldName(final DetailAST methodCallDotAst) {
        String nameOfSettingField = null;
        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 aFieldName
     *        - name of the field.
     * @param aMethodName
     *        - part of name of the method (without "set", "get" or "is").
     * @return true when names are different.
     */
    private boolean verifyFieldAndMethodName(String aFieldName, String aMethodName) {
        return aFieldName.equalsIgnoreCase(mFieldPrefix + aMethodName);
    }

    /**
     * <p>
     * Return name of the field, that use in the getter.
     * </p>
     * @param aExpr
     *        - DetailAST contains expression from getter.
     * @return name of the field, that use in getter.
     */
    private static String getNameOfGetterField(DetailAST aExpr) {
        String nameOfGetterField = null;

        if (aExpr.getChildCount() == 1) {
            final DetailAST exprFirstChild = aExpr.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 aMethodAST
     *        DetailAST instance.
     * @return true if aMethodAST is a main method, false otherwise.
     */
    private static boolean isMainMethod(final DetailAST aMethodAST) {
        boolean result = true;
        final String methodName = getIdentifier(aMethodAST);
        if ("main".equals(methodName)) {
            result = isVoidType(aMethodAST) && isMainMethodModifiers(aMethodAST)
                    && isMainMethodParameters(aMethodAST);
        } else {
            result = false;
        }
        return result;
    }

    /**
     * Verifies that given AST has appropriate modifiers for main method.
     * @param aMethodAST
     *        DetailAST instance.
     * @return true if aMethodAST has (public & static & !abstract) modifiers,
     *         false otherwise.
     */
    private static boolean isMainMethodModifiers(final DetailAST aMethodAST) {
        boolean result = false;
        if (hasChildToken(aMethodAST, TokenTypes.MODIFIERS)) {
            final DetailAST modifiers = aMethodAST.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 aMethodAST
     *        DetailAST instance.
     * @return true if AST's type void, false otherwise.
     */
    private static boolean isVoidType(final DetailAST aMethodAST) {
        boolean result = true;
        DetailAST methodTypeAST = null;
        if (hasChildToken(aMethodAST, TokenTypes.TYPE)) {
            methodTypeAST = aMethodAST.findFirstToken(TokenTypes.TYPE);
            result = hasChildToken(methodTypeAST, TokenTypes.LITERAL_VOID);
        }
        return result;
    }

    /**
     * Verifies that given AST has appropriate for main method parameters.
     * @param aMethodAST
     *        instance of a method
     * @return true if parameters of aMethodAST are appropriate for main method,
     *         false otherwise.
     */
    private static boolean isMainMethodParameters(final DetailAST aMethodAST) {
        final DetailAST params = aMethodAST.findFirstToken(TokenTypes.PARAMETERS);
        return hasOnlyStringArrayParameter(params) || hasOnlyStringEllipsisParameter(params);
    }

    /**
     * Return true if AST of method parameters has String[] parameter child
     * token.
     * @param aParametersAST
     *        DetailAST of method parameters.
     * @return true if AST has String[] parameter child token, false otherwise.
     */
    private static boolean hasOnlyStringArrayParameter(final DetailAST aParametersAST) {
        boolean result = true;
        if (aParametersAST.getChildCount(TokenTypes.PARAMETER_DEF) != 1) {
            result = false;
        } else { // there is one parameter
            final DetailAST parameterDefinitionAST = aParametersAST.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;
            }
        }
        return result;
    }

    /**
     * Return true if AST of method parameters has String... parameter child
     * token.
     * @param aParametersAST
     *        DetailAST of method parameters.
     * @return true if aParametersAST has String... parameter child token, false
     *         otherwise.
     */
    private static boolean hasOnlyStringEllipsisParameter(final DetailAST aParametersAST) {
        boolean result = true;
        if (aParametersAST.getChildCount(TokenTypes.PARAMETER_DEF) != 1) {
            result = false;
        }
        // there is one parameter
        else {
            final DetailAST parameterDefinitionAST = aParametersAST.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;
            }
        }
        return result;
    }

    /**
     * Return true if aAST has token of aTokenType type.
     * @param aAST
     *        DetailAST instance.
     * @param aTokenType
     *        one of TokenTypes
     * @return true if aAST has token of given type, or false otherwise.
     */
    private static boolean hasChildToken(DetailAST aAST, int aTokenType) {
        return aAST.findFirstToken(aTokenType) != null;
    }

    /**
     * private class for members of class and their patterns.
     */
    private static class FormatMatcher {
        /** The regexp to match against */
        private Pattern mRegExp;
        /** The Member of Class */
        private final int mClassMember;
        /** The input full one rule with original names */
        private final String mRule;
        /** The string format of the RegExp */
        private String mFormat;

        /**
         * Creates a new <code>FormatMatcher</code> instance.
         *
         * @param aInputRule input string with MemberDefinition and RegExp.
         * @param aClassMember the member of class
         * @param aCompileFlags the Pattern flags to compile the regexp with.
         *            See {@link Pattern#compile(java.lang.String, int)}
         */
        public FormatMatcher(final String aInputRule, final int aClassMember) {
            mClassMember = aClassMember;
            mRule = aInputRule;
        }

        /** @return the RegExp to match against */
        public final Pattern getRegexp() {
            return mRegExp;
        }

        /** @return the original immutable input rule */
        public final String getRule() {
            return mRule;
        }

        /** @return the Class Member */
        public final int getClassMember() {
            return mClassMember;
        }

        /**
         * Set the compile flags for the regular expression.
         *
         * @param aCompileFlags the compile flags to use.
         */
        public final void setCompileFlags(final int aCompileFlags) {
            updateRegexp(mFormat, aCompileFlags);
        }

        /**
         * Updates the regular expression using the supplied format and compiler
         * flags. Will also update the member variables.
         *
         * @param aFormat the format of the regular expression.
         * @param aCompileFlags the compiler flags to use.
         */
        private void updateRegexp(final String aFormat, final int aCompileFlags) {
            try {
                mRegExp = Utils.getPattern(aFormat, aCompileFlags);
                mFormat = aFormat;
            } catch (final PatternSyntaxException e) {
                throw new ConversionException("unable to parse " + aFormat, e);
            }
        }

        /**
         * Check that format matcher contains rule.
         * @param aRule string
         * @return true if format matcher contains rule.
         */
        public boolean hasRule(String aRule) {
            return mRule.indexOf(aRule) > -1;
        }

        @Override
        public String toString() {
            return mRule;
        }
    }

    /**
     * Class to keep current position and collect getters, setters.
     */
    private static class ClassDetail {
        /**
         * Current position in custom order declaration
         */
        private int mCurrentPosition;
        /**
         * List of getter ASTs
         */
        private final List<DetailAST> mGetters = new LinkedList<DetailAST>();
        /**
         * List of setter ASTs
         */
        private final List<DetailAST> mSetters = new LinkedList<DetailAST>();

        public int getCurrentPosition() {
            return mCurrentPosition;
        }

        public void setCurrentPosition(int aPosition) {
            mCurrentPosition = aPosition;
        }

        /**
         * Add getter.
         * @param aGetterAst DetailAST of getter.
         */
        public void addGetter(DetailAST aGetterAst) {
            mGetters.add(aGetterAst);
        }

        /**
         * Add setter.
         * @param aSetterAst DetailAST of setter.
         */
        public void addSetter(DetailAST aSetterAst) {
            mSetters.add(aSetterAst);
        }

        /**
         * 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<DetailAST, DetailAST>();
            if (!mGetters.isEmpty() & !mSetters.isEmpty()) {
                //  all getters and setters
                final List<DetailAST> allGettersSetters = new ArrayList<DetailAST>(mGetters);
                allGettersSetters.addAll(mSetters);
                // sort by line numbers
                Collections.sort(allGettersSetters, AST_LINE_COMPARATOR);

                for (int i = 0; i < allGettersSetters.size(); i++) {
                    final DetailAST getterAst = allGettersSetters.get(i);
                    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);
                    }

                    if (getterField != null) {
                        // review rest of the list to find a proper setter
                        for (int j = 0; j < allGettersSetters.size(); j++) {
                            if (i == j) { // method is getter
                                continue;
                            }
                            final DetailAST setterAst = allGettersSetters.get(j);
                            final String setterName = getIdentifier(setterAst);
                            String setterField = null;
                            if (isSetterName(setterName)) {
                                setterField = getNameWithoutPrefix(getIdentifier(setterAst), SETTER_PREFIX);
                            } else { // non-setter method
                                continue;
                            }
                            // if fields are same and setter is sibling with getter
                            if (getterField.equals(setterField) && j != (i + 1)) {
                                result.put(setterAst, getterAst);
                                break;
                            }
                        }
                    }
                }
            }
            return result;
        }

        /**
         * Verify that specified method was saved as getter.
         * @param aMethodName name of method.
         * @return true if specified method was saved as getter.
         */
        private boolean containsGetter(String aMethodName) {
            boolean result = false;
            for (DetailAST methodAst : mGetters) {
                final String methodName = getIdentifier(methodAst);
                if (methodName.equals(aMethodName)) {
                    result = true;
                }
            }
            return result;
        }

        /**
         * Verify that specified method was saved as setter.
         * @param aMethodName name of method.
         * @return true if specified method was saved as setter.
         */
        private boolean containsSetter(String aMethodName) {
            boolean result = false;
            for (DetailAST methodAst : mSetters) {
                final String methodName = getIdentifier(methodAst);
                if (methodName.equals(aMethodName)) {
                    result = true;
                }
            }
            return result;
        }
    }

}