////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2016 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.io.Serializable;
import java.util.LinkedList;
import java.util.List;

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

/**
 * <p>
 * This check prevents any calls to overridable methods that are take place in:
 * <ol><li>
 * Any constructor body (verification is always done by default and not
 * configurable).
 * <li>
 * Any method which works same as a constructor: clone() method from Cloneable
 * interface and readObject() method from Serializable interface (you can
 * individually switch on/off these methods verification by changing
 * CheckCloneMethod and CheckReadObjectMethod properties).</li>
 * </ol>
 * Rationale:
 * <ol>
 * <li>Constructors must not invoke overridable methods, directly or
 * indirectly. If you violate this rule, program failure will result. The
 * superclass constructor runs before the subclass constructor, so the
 * overriding method in the subclass will be invoked before the subclass
 * constructor has run. If the overriding method depends on any
 * initialization performed by the subclass constructor, the method will
 * not behave as expected.
 * <li>If you do decide to implement Cloneable or Serializable in a class
 * designed for inheritance, you should be aware that because the clone and
 * readObject methods behave a lot like constructors, a similar restriction
 * applies: neither clone nor readObject may invoke an overridable method,
 * directly or indirectly.
 * </ol>
 * [Joshua Bloch - Effective Java 2nd Edition, Chapter 4, Item 17]
 * <br> Here's an example to illustrate: <pre>
 * public class Example {
 *    public static void main(String[] args) {
 *        abstract class Base {
 *            Base() { overrideMe(); }
 *            abstract void overrideMe();
 *        }
 *        class Child extends Base {
 *            final int x;
 *            Child(int x) { this.x = x; }
 *            void overrideMe() {
 *                System.out.println(x);
 *            }
 *        }
 *        new Child(42); // prints "0"
 *    }
 * }</pre>
 * <p>
 * Here, when Base constructor calls overrideMe, Child has not finished
 * initializing the final int x, and the method gets the wrong value. This will
 * almost certainly lead to bugs and errors.
 * </p>
 * <p>
 * <i><b>Notes:</b><br><br>This check doesn`t handle the situation when there
 * is a call to an overloaded method(s).</i><br>Here`s an example:
 *
 * <pre> public class Test {
 *
 *   public static void main(String[] args) {
 *
 *       class Base {
 *           Base() {
 *               System.out.println("Base C-tor ");
 *               overrideMe("Foo!"); // no warnings here, because the method
 *                                   // named "overrideMe" is overloaded.
 *           }
 *           void overrideMe() { }
 *           void overrideMe(String str) {
 *               System.out.println("Base overrideMe(String str) ");
 *           }
 *       }
 *
 *       class Child extends Base {
 *           final int x;
 *           Child(int x) {
 *               this.x = x;
 *           }
 *           void overrideMe(String str) {
 *               System.out.println("Child`s overrideMe(): " + x);
 *           }
 *       }
 *     new Child(999);
 *   }
 * } </pre>
 *
 *<p><br>
 * <i>Some specific method call types that aren`t supported by check:</i>
 * </p>
 * <ul>
 * <li>BaseClass.InnerClass.this.methodName();</li>
 * <li>InnerClass.this.methodName();</li>
 * <li>and so on, using a similar hierarchy</li>
 * </ul>
 *<br>
 *
 * @author <a href="mailto:Daniil.Yaroslavtsev@gmail.com"> Daniil
 *         Yaroslavtsev</a>
 * @author <a href="mailto:IliaDubinin91@gmail.com">Ilja Dubinin</a>
 */
public class OverridableMethodInConstructorCheck extends Check
{

    /**
     * A key is pointing to the warning message text in "messages.properties"
     * file.
     * */
    public static final String MSG_KEY = "overridable.method";
    
    /**
     * A key is pointing to the warning message text in "messages.properties"
     * file.
     * */
    public static final String MSG_KEY_LEADS = "overridable.method.leads";

    /**
     * A key is using to build a warning message about calls of an overridable
     * methods from any constructor body.
     * */
    private static final String KEY_CTOR = "constructor";

    /**
     * A key is using to build a warning message about calls of an overridable
     * methods from any clone() method is implemented from Cloneable interface.
     * */
    private static final String KEY_CLONE = "'clone()' method";

    /**
     * A key is using to build a warning message about calls of an overridable
     * methods from any readObject() method is implemented from Serializable
     * interface.
     * */
    private static final String KEY_READ_OBJECT = "'readObject()' method";

    /**
     * A list contains all METHOD_CALL DetailAST nodes that have been already
     * visited by check.
     * */
    private final List<DetailAST> visitedMethodCalls = new LinkedList<DetailAST>();

    /**
     * A current MethodDef AST is being processed by check.
     * */
    private DetailAST curMethodDef;

    /**
     * A current root of the synthax tree is being processed.
     * */
    private DetailAST treeRootAST;

    /**
     * A boolean check box that enables the searching of calls to overridable
     * methods from the body of any clone() method is implemented from Cloneable
     * interface.
     * */
    private boolean checkCloneMethod;

    /**
     * A boolean check box that enables the searching of calls to overridable
     * methods from the body of any readObject() method is implemented from
     * Serializable interface.
     */
    private boolean checkReadObjectMethod;
    
    /**
     * A boolean check box that enables the matching methods by number of 
     * their parameters.
     */
    private boolean matchMethodsByArgCount;

    /**
     * The name of current overridable method is being processed.
     */
    private String curOverridableMetName;

    /**
     * Method definitions counter for class is currently being processed.
     */
    private int curMethodDefCount;

    /**
     * Enable|Disable searching of calls to overridable methods from body of any
     * clone() method is implemented from Cloneable interface.
     *
     * @param value
     *            The state of a boolean check box that enables the searching of
     *            calls to overridable methods from body of any clone() method
     *            is implemented from Cloneable interface.
     */
    public void setCheckCloneMethod(final boolean value)
    {
        checkCloneMethod = value;
    }
    
    /**
     * Enable|Disable matching methods by arguments count
     *
     * @param value
     *            The state of a boolean check box that enables the matching of
     *            methods by arguments count.
     */
    public void setMatchMethodsByArgCount(final boolean value)
    {
        matchMethodsByArgCount = value;
    }

    /**
     * Enable|Disable searching of calls to overridable methods from body of any
     * readObject() method is implemented from Serializable interface.
     *
     * @param value
     *            The state of a boolean check box that enables the searching of
     *            calls to overridable methods from body of any readObject()
     *            method is implemented from Serializable interface.
     */
    public void setCheckReadObjectMethod(final boolean value)
    {
        checkReadObjectMethod = value;
    }

    @Override
    public int[] getDefaultTokens()
    {
        return new int[] {TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF};
    }

    @Override
    public void beginTree(DetailAST rootAST)
    {
        treeRootAST = rootAST;
    }

    @Override
    public void visitToken(final DetailAST detailAST)
    {

        final DetailAST classDef = getClassDef(detailAST);

        if (classDef != null && !hasModifier(classDef, TokenTypes.FINAL)) {

            switch (detailAST.getType()) {

                case TokenTypes.CTOR_DEF:
                logWarnings(detailAST, KEY_CTOR);
                    break;

                case TokenTypes.METHOD_DEF:

                    final String methodName = detailAST.findFirstToken(
                        TokenTypes.IDENT).getText();

                    if (checkCloneMethod && "clone".equals(methodName)
                        && realizesAnInterface(classDef, Cloneable.class.getSimpleName()))
                    {
                    logWarnings(detailAST, KEY_CLONE);
                    }
                    else if (checkReadObjectMethod
                        && "readObject".equals(methodName)
                        && realizesAnInterface(classDef, Serializable.class.getSimpleName()))
                    {
                    logWarnings(detailAST, KEY_READ_OBJECT);
                    }
                    break;

                default:
                    Utils.reportInvalidToken(detailAST.getType());
                    break;
            }
        }
    }

    /**
     * Gets all METHOD_CALL DetailASTs that are pointing to overridable methods
     * calls from current method or c-tor body and logs them.
     *
     * @param detailAST
     *            A DetailAST node which is pointing to current method or c-tor
     *            body is being processed to retrieve overridable method calls
     *            list.
     * @param key
     *            A string is using to retrieve the warning message text from
     *            "messages.properties" file.
     */
    private void logWarnings(final DetailAST detailAST, final String key)
    {

        final List<OverridableMetCall> methodCallsToWarnList =
            getOverridables(detailAST);

        for (OverridableMetCall om : methodCallsToWarnList) {
            final DetailAST methodDef = getMethodDef(om.metCallAST);
            if (hasModifier(methodDef, TokenTypes.LITERAL_PRIVATE)
                    || hasModifier(methodDef, TokenTypes.FINAL))
            {
                log(om.metCallAST, MSG_KEY_LEADS, getMethodName(om.metCallAST),
                        key, om.overridableMetName);
            } else {
                log(om.metCallAST, MSG_KEY, getMethodName(om.metCallAST),
                    key, om.overridableMetName);
               }
        }
    }

    /**
     * Searches for all METHOD_CALL DetailASTs that are pointing to overridable
     * methods calls in current method or c-tor body and generates a list of
     * them.
     *
     * @param parentAST
     *            A DetailAST METHOD_DEF of CTOR_DEF node which is pointing to
     *            the current method or c-tor body is being processed to
     *            retrieve overridable method calls.
     * @return A list of overridable methods calls for current method or
     *         constructor body.
     */
    private List<OverridableMetCall> getOverridables(final DetailAST parentAST)
    {

        final List<OverridableMetCall> result =
            new LinkedList<OverridableMetCall>();
        final List<DetailAST> methodCallsList = getMethodCallsList(parentAST);

        for (DetailAST curNode : methodCallsList) {
            visitedMethodCalls.clear();
            final DetailAST methodDef = getMethodDef(curNode);
            if (methodDef != null
                    && getMethodParamsCount(curNode)
                        == getMethodParamsCount(methodDef)
                    && isOverridableMethodCall(curNode))
            {
                result.add(new OverridableMetCall(curNode,
                        curOverridableMetName));
            }
        }
        return result;
    }

    /**
     * Checks that current processed METHOD_CALL DetailAST is pointing to
     * overridable method call.
     *
     * @param methodCallAST
     *            A METHOD_CALL DetailAST is currently being processed.
     * @return true if current processed METHOD_CALL node is pointing to the
     *         overridable method call and false otherwise.
     */
    private boolean isOverridableMethodCall(final DetailAST methodCallAST)
    {

        boolean result = false;
        visitedMethodCalls.add(methodCallAST);

        final String methodName = getMethodName(methodCallAST);
        final DetailAST methodDef = getMethodDef(methodCallAST);

        if (methodName != null
                && methodDef != null)
        {
            if (hasModifier(methodDef, TokenTypes.LITERAL_STATIC)) {
                // do nothing
            }
            else if (hasModifier(methodDef, TokenTypes.LITERAL_PRIVATE)
                    || hasModifier(methodDef, TokenTypes.FINAL))
            {
                final List<DetailAST> methodCallsList = getMethodCallsList(
                        methodDef);
                for (DetailAST curNode : methodCallsList) {
                    if (!visitedMethodCalls.contains(curNode)
                            && isOverridableMethodCall(curNode)) {
                        result = true;
                        break;
                    }
                }
            }
            else {
                curOverridableMetName = methodName;
                result = true;
            }
        }
        return result;
    }

   /**
     * Gets all METHOD_CALL nodes which are below on the current parent
     * METHOD_DEF or CTOR_DEF node.
     *
     * @param parentAST
     *            The current parent METHOD_DEF or CTOR_DEF node.
     * @return List contains all METHOD_CALL nodes which are below on the
     *         current parent node.
     */
    private List<DetailAST> getMethodCallsList(final DetailAST parentAST)
    {

        final List<DetailAST> result = new LinkedList<DetailAST>();

        for (DetailAST curNode : getChildren(parentAST)) {
            if (curNode.getNumberOfChildren() > 0) {
                if (curNode.getType() == TokenTypes.METHOD_CALL) {
                    result.add(curNode);
                }
                else {
                    result.addAll(getMethodCallsList(curNode));
                }
            }
        }
        return result;
    }

    /**
     * Gets the method name is related to the current METHOD_CALL DetailAST.
     *
     * @param methodCallAST
     *            A METHOD_CALL DetailAST node is currently being processed.
     * @return The method name is related to the current METHOD_CALL DetailAST.
     */
    private String getMethodName(final DetailAST methodCallAST)
    {

        String result = null;

        final DetailAST ident = methodCallAST.findFirstToken(TokenTypes.IDENT);

        if (ident != null) {
            result = ident.getText();
        }
        else {
            final DetailAST childAST = methodCallAST.getFirstChild();

            if (childAST != null && childAST.getType() == TokenTypes.DOT) {

                final DetailAST firstChild = childAST.getFirstChild();
                final DetailAST lastChild = childAST.getLastChild();

                if (firstChild.getType() == TokenTypes.LITERAL_THIS
                        || firstChild.getType() == TokenTypes.LPAREN
                        || firstChild.getType() == TokenTypes.DOT)
                {
                    result = lastChild.getText();
                }
                else if (firstChild.getType() == TokenTypes.IDENT
                        && lastChild.getType() == TokenTypes.IDENT)
                {
                    final String curClassName = getClassDef(methodCallAST)
                            .findFirstToken(TokenTypes.IDENT).getText();
                    if (firstChild.getText().equals(curClassName)
                            || getClassDef(treeRootAST,
                                    firstChild.getText()) != null)
                    {
                        result = lastChild.getText();
                    }
                }
            }
        }
        return result;
    }

    /**
     * Gets the method definition is related to the current METHOD_CALL
     * DetailAST node. If method definition doesn't find, will returned null.
     * @param methodCallAST
     *            A METHOD_CALL DetailAST node is currently being processed.
     * @return the METHOD_DEF DetailAST node is pointing to the method
     *         definition which is related to the current METHOD_CALL DetailAST
     *         node.
     */
    private DetailAST getMethodDef(final DetailAST methodCallAST)
    {

        DetailAST result = null;

        curMethodDef = null;
        curMethodDefCount = 0;

        final String methodName = getMethodName(methodCallAST);
        if (methodName != null) {

            final DetailAST curClassAST = getClassDef(methodCallAST);
            final DetailAST callsChild = methodCallAST.getFirstChild();
            String variableTypeName;

            if (callsChild.getType() != TokenTypes.DOT ||
                    (variableTypeName = getVariableType(methodCallAST)) == null
                    || (isItTypeOfCurrentClass(variableTypeName, curClassAST) ||
                    isItCallMethodViaKeywordThis(variableTypeName, curClassAST)))
            {
                getMethodDef(curClassAST, methodName);
            }

            if (curMethodDefCount == 0) {

                final List<DetailAST> baseClasses = getBaseClasses(curClassAST);

                for (DetailAST curBaseClass : baseClasses) {
                    curMethodDef = null;
                    curMethodDefCount = 0;
                    getMethodDef(curBaseClass, methodName);
                    if (curMethodDefCount == 1) {
                        result = curMethodDef;
                        break;
                    }
                }
            }
            else if (curMethodDefCount == 1) {
                result = curMethodDef;
            }
            else
            {
                if(matchMethodsByArgCount)
                {
                    int sameDefinitionCounter = 0;
                    int curMethodParamCount = 
                            getMethodParamsCount(methodCallAST);
                    for(DetailAST currentDefinition :
                        getMethodDef(curClassAST, methodName))
                    {
                        if(getMethodParamsCount(currentDefinition) ==
                                curMethodParamCount )
                        {
                            result = currentDefinition;
                            sameDefinitionCounter++;
                        }
                    }
                    //you have a lot same method definitions and you can't 
                    //select one of them and be sure that you are right
                    if(sameDefinitionCounter > 1)
                    {
                        result = null;
                    }
                }
            }
        }
        return result;
    }

    /**
     * Return type of the variable, if it is declaration procedure.
     * @param methodCall
     * @return variables type name
     */
    private static String getVariableType(DetailAST methodCall)
    {
        DetailAST callsChild = methodCall.getFirstChild();
        String typeName = new String();
        if (callsChild.getType() == TokenTypes.DOT)
        {
            DetailAST dotChild = callsChild.getFirstChild();
            if (dotChild.getType() == TokenTypes.LITERAL_THIS)
            {
                typeName = "this";
            }
            else if (callsChild.getChildCount(TokenTypes.TYPECAST) > 0)
            {
                DetailAST typeCast = callsChild
                        .findFirstToken(TokenTypes.TYPECAST);
                DetailAST type = typeCast.getFirstChild().getFirstChild();
                typeName = type.getText();
            }
            else if (dotChild.getType() == TokenTypes.DOT)
            {
                typeName = dotChild.getFirstChild().getText() +
                        "." + dotChild.getLastChild().getText();
            }

        }
        return typeName;
    }

    /**
     * Return true when usedIbjectName contains current class name or base class
     * name.
     * @param usedObjectName
     * @param classDefNode
     * @return
     */
    private static boolean
            isItTypeOfCurrentClass(String objectTypeName, DetailAST classDefNode)
    {
        DetailAST className = classDefNode.findFirstToken(TokenTypes.IDENT);
        boolean result = false;
        if (objectTypeName.equals(className.getText()))
        {
            result = true;
        }
        else
        {
            DetailAST baseClass = classDefNode.findFirstToken(
                    TokenTypes.EXTENDS_CLAUSE);
            if (baseClass != null)
            {
                baseClass = baseClass.getFirstChild();

                if (objectTypeName.equals(baseClass.getText()))
                {
                    result = true;
                }
            }
        }
        return result;
    }
    
    /**
     * Return true when the method called via keyword "this" (this.methodName 
     * or ClassName.this.methodName)
     * @param firstPartOfTheMethodCall
     * @param classDefNode
     * @return
     */
    private static boolean
            isItCallMethodViaKeywordThis(String firstPartOfTheMethodCall, DetailAST classDefNode)
    {
        String className = classDefNode.findFirstToken(TokenTypes.IDENT).getText();
        return "this".equals(firstPartOfTheMethodCall) 
                || (className + ".this").equals(firstPartOfTheMethodCall);
    }
    
    /**
     * Gets the method definition is related to the current METHOD_CALL
     * DetailAST using the name of method to be searched. Ignores overloaded
     * methods (retrieves only one METHOD_DEF node).
     *
     * @param parentAST
     *            A parent CLASS_DEF DetailAST node which uses as a start point
     *            when searching.
     * @param methodName
     *            String containing the name of method is currently being
     *            searched.
     * @return a List of method definition
     */
    private List<DetailAST> getMethodDef(final DetailAST parentAST,
            final String methodName)
    {
        List<DetailAST> definitionsList = new LinkedList<DetailAST>();
        
        for (DetailAST curNode : getChildren(parentAST)) {

            if (curNode.getNumberOfChildren() > 0) {
                if (curNode.getType() == TokenTypes.METHOD_DEF) {
                    final String curMethodName = curNode.findFirstToken(
                            TokenTypes.IDENT).getText();
                    if (methodName.equals(curMethodName)) {
                        curMethodDef = curNode;
                        definitionsList.add(0,curNode);
                        curMethodDefCount++;
                    }
                }

                final int type = curNode.getType();

                if (type != TokenTypes.CLASS_DEF && type != TokenTypes.CTOR_DEF
                        && type != TokenTypes.MODIFIERS
                        && type != TokenTypes.IMPLEMENTS_CLAUSE
                        && type != TokenTypes.METHOD_DEF)
                {
                    definitionsList = getMethodDef(curNode, methodName);
                }
            }
        }
        return definitionsList;
    }

    /**
     * Gets the count of parameters for current method definitioin or
     * method call.
     * @param methodDefOrCallAST METHOD_DEF or METHOD_CALL
     *     DetailAST node
     * @return the count of parameters for current method.
     */
    private static int getMethodParamsCount(DetailAST methodDefOrCallAST)
    {

        int result = 0;
        DetailAST paramsParentAST = null;

        if (methodDefOrCallAST.getType() == TokenTypes.METHOD_CALL) {
            paramsParentAST = methodDefOrCallAST
                    .findFirstToken(TokenTypes.ELIST);
        }
        else if (methodDefOrCallAST.getType() == TokenTypes.METHOD_DEF) {
            paramsParentAST = methodDefOrCallAST
                    .findFirstToken(TokenTypes.PARAMETERS);
        }

        if (paramsParentAST != null && paramsParentAST.getChildCount() != 0) {
            for (DetailAST curNode : getChildren(paramsParentAST)) {
                if (curNode.getType() == TokenTypes.COMMA) {
                    result++;
                }
            }
            result++;
        }
        return result;
    }

    /**
     * Checks that method or class is related to the current METHOD_DEF or
     * CLASS_DEF DetailAST node has a specified modifier (private, final etc).
     *
     * @param methodOrClassDefAST
     *            A METHOD_DEF or CLASS_DEF DetailAST node is currently being
     *            processed.
     * @param modifierType
     *            desired modifier type.
     * @return true if method is related to current aMethodDefAST METHOD_DEF
     *         node has "private" or "final" modifier and false otherwise.
     */
    private static boolean hasModifier(final DetailAST methodOrClassDefAST,
        int modifierType)
    {

        boolean result = false;
        final DetailAST modifiers = methodOrClassDefAST
                .findFirstToken(TokenTypes.MODIFIERS);

        if (modifiers != null && modifiers.getChildCount() != 0) {
            for (DetailAST curNode : getChildren(modifiers)) {
                if (curNode.getType() == modifierType) {
                    result = true;
                    break;
                }
            }
        }
        return result;
    }


    /**
     * Gets a parent CLASS_DEF DetailAST node for current METHOD_CALL DetailAST
     * node.
     *
     * @param methodNode
     *            A METHOD_DEF or METHOD_CALL DetailAST node for current method.
     * @return The parent CLASS_DEF node for the class that owns a METHOD_CALL
     *         node named aMethodNode.
     * */
    private static DetailAST getClassDef(final DetailAST methodNode)
    {

        DetailAST curNode = methodNode;

        while (curNode != null && curNode.getType() != TokenTypes.CLASS_DEF) {
            curNode = curNode.getParent();
        }

        return curNode;
    }

    /**
     * Checks that class realizes "anInterfaceName" interface (checks that class
     * implements this interface or has at least one parent class which
     * implements this interface).
     *
     * @param classDefNode
     *            A CLASS_DEF DetailAST for class is currently being checked.
     * @param interfaceName
     *            The name of the interface to check.
     * @return true if class realizes "anInterfaceName" interface and false
     *         otherwise.
     */
    private boolean realizesAnInterface(final DetailAST classDefNode,
            final String interfaceName)
    {

        boolean result = false;
        final List<DetailAST> classWithBaseClasses =
                getBaseClasses(classDefNode);

        classWithBaseClasses.add(classDefNode);

        for (DetailAST classAST : classWithBaseClasses) {
            if (implementsAnInterface(classAST, interfaceName)) {
                result = true;
                break;
            }
        }
        return result;
    }

    /**
     * Checks that class implements "anInterfaceName" interface.
     *
     * @param classDefAST
     *            A CLASS_DEF DetailAST for class is currently being checked.
     * @param interfaceName
     *            The name of the interface to check.
     * @return true if class is related to the current CLASS_DEF DetailAST is
     *         being processed implements "anInterfaceName" interface and false
     *         otherwise.
     */
    private static boolean implementsAnInterface(final DetailAST classDefAST,
            final String interfaceName)
    {

        boolean result = false;
        final DetailAST implClause = classDefAST
                .findFirstToken(TokenTypes.IMPLEMENTS_CLAUSE);

        if (implClause != null) {
            for (DetailAST ident : getChildren(implClause)) {
                if (ident.getText().equals(interfaceName)) {
                    result = true;
                    break;
                }
            }
        }
        return result;
    }

    /**
     * Gets the list of CLASS_DEF DetailAST nodes that are associated with class
     * is currently being processed and all it`s base classes.
     *
     * @param classDefNode
     *            A CLASS_DEF DetailAST is related to the class is currently
     *            being processed.
     * @return a list of CLASS_DEF DetailAST nodes for class is currently being
     *         processed and all it`s base classes.
     */
    private List<DetailAST> getBaseClasses(final DetailAST classDefNode)
    {

        final List<DetailAST> result = new LinkedList<DetailAST>();
        String baseClassName = getBaseClassName(classDefNode);

        if (baseClassName != null) {
            DetailAST curClass = getClassDef(treeRootAST, baseClassName);
            while (curClass != null) {
                result.add(curClass);
                baseClassName = getBaseClassName(curClass);
                if (baseClassName != null) {
                    curClass = getClassDef(treeRootAST, baseClassName);
                }
                else {
                    break;
                }
            }
        }
        return result;
    }

    /**
     * Gets the CLASS_DEF DetailAST node for the class is named "aClassName".
     *
     * @param rootNode
     *            A root node of synthax tree is being processed.
     * @param className
     *            The name of class to search.
     * @return The CLASS_DEF DetailAST node which is related to the class is
     *         named "aClassName".
     */
    private static DetailAST getClassDef(DetailAST rootNode, String className)
    {

        DetailAST curNode = rootNode;

        while (curNode != null) {
            DetailAST toVisit = curNode.getFirstChild();
            while (curNode != null && toVisit == null) {
                toVisit = curNode.getNextSibling();
                if (toVisit == null) {
                    curNode = curNode.getParent();
                }
            }

            curNode = toVisit;

            if (curNode != null
                    && curNode.getType() == TokenTypes.CLASS_DEF
                    && curNode.findFirstToken(TokenTypes.IDENT).getText()
                            .equals(className))
            {
                break;
            }
        }
        return curNode;
    }

    /**
     * Gets the the base class name for current class.
     *
     * @param classDefNode
     *            A CLASS_DEF DetailAST.
     * @return The name of a base class for current class.
     */
    private static String getBaseClassName(final DetailAST classDefNode)
    {

        String result = null;
        final DetailAST extendsClause = classDefNode
                .findFirstToken(TokenTypes.EXTENDS_CLAUSE);

        if (extendsClause != null) {
            final DetailAST dot = extendsClause.findFirstToken(TokenTypes.DOT);
            if (dot != null) {
                result = dot.findFirstToken(TokenTypes.IDENT).getText();
            }
            else {
                result = extendsClause.findFirstToken(TokenTypes.IDENT)
                        .getText();
            }
        }
        return result;
    }

    /**
     * Gets all the children one level below on the current DetailAST parent
     * node.
     *
     * @param node
     *            Current parent node.
     * @return An array of children one level below on the current parent node
     *         aNode.
     */
    private static List<DetailAST> getChildren(final DetailAST node)
    {
        final List<DetailAST> result = new LinkedList<DetailAST>();
        DetailAST curNode = node.getFirstChild();
        while (curNode != null) {
            result.add(curNode);
            curNode = curNode.getNextSibling();
        }
        return result;
    }

    /**
     * Class that incapsulates the DetailAST node related to the method call
     * that leads to call of the overridable method and the name of
     * overridable method.
     */
    private class OverridableMetCall
    {
        /**
         * DetailAST node is related to the method call that leads to
         *           call of the overridable method.
         */
        private DetailAST metCallAST;
        /**
         * The name of an overridable method.
         */
        private String overridableMetName;

        /**
         * Creates an instance of OverridableMetCall and initializes fields.
         * @param methodCallAST
         *            DetailAST node related to the method call that leads
         *            to call of the overridable method.
         * @param overridableMetName
         *            The name of an overridable method.
         */
        public OverridableMetCall(DetailAST methodCallAST,
                String overridableMetName)
        {
            super();
            this.metCallAST = methodCallAST;
            this.overridableMetName = overridableMetName;
        }
    }

}
