/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */
package net.sourceforge.pmd.lang.java.rule.sunsecure;

import java.util.List;

import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression;
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;

/**
 * Utility methods for the package
 *
 * Created on Jan 17, 2005
 * 
 * @author mgriffa
 */
public abstract class AbstractSunSecureRule extends AbstractJavaRule {

    /**
     * Tells if the type declaration has a field with varName.
     *
     * @param varName
     *            the name of the field to search
     * @param typeDeclaration
     *            the type declaration
     * @return <code>true</code> if there is a field in the type declaration
     *         named varName, <code>false</code> in other case
     */
    protected final boolean isField(String varName, ASTTypeDeclaration typeDeclaration) {
        final List<ASTFieldDeclaration> fds = typeDeclaration.findDescendantsOfType(ASTFieldDeclaration.class);
        if (fds != null) {
            for (ASTFieldDeclaration fd : fds) {
                final ASTVariableDeclaratorId vid = fd.getFirstDescendantOfType(ASTVariableDeclaratorId.class);
                if (vid != null && vid.hasImageEqualTo(varName)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Gets the name of the variable returned. Some examples: <br>
     * for this.foo returns foo <br>
     * for foo returns foo <br>
     * for foo.bar returns foo.bar
     *
     * @param ret
     *            a return statement to evaluate
     * @return the name of the variable associated or <code>null</code> if it
     *         cannot be detected
     */
    protected final String getReturnedVariableName(ASTReturnStatement ret) {
        if (hasTernaryCondition(ret) && hasTernaryNullCheck(ret)) {
            return ret.getFirstDescendantOfType(ASTConditionalExpression.class).jjtGetChild(0)
                        .getFirstDescendantOfType(ASTName.class).getImage();
        }

        final ASTName n = ret.getFirstDescendantOfType(ASTName.class);
        if (n != null) {
            return n.getImage();
        }
        final ASTPrimarySuffix ps = ret.getFirstDescendantOfType(ASTPrimarySuffix.class);
        if (ps != null) {
            return ps.getImage();
        }
        return null;
    }

    private boolean hasTernaryNullCheck(ASTReturnStatement ret) {
        ASTConditionalExpression condition = ret.getFirstDescendantOfType(ASTConditionalExpression.class);
        return condition.jjtGetChild(0) instanceof ASTEqualityExpression
                && condition.jjtGetChild(0).hasImageEqualTo("==")
                && condition.jjtGetChild(0).jjtGetChild(0).hasDescendantOfType(ASTName.class)
                && condition.jjtGetChild(0).jjtGetChild(1).hasDescendantOfType(ASTNullLiteral.class);
    }

    private boolean hasTernaryCondition(ASTReturnStatement ret) {
        ASTConditionalExpression condition = ret.getFirstDescendantOfType(ASTConditionalExpression.class);
        return condition != null && condition.isTernary();
    }

    /**
     * TODO modify usages to use symbol table Tells if the variable name is a
     * local variable declared in the method.
     *
     * @param vn
     *            the variable name
     * @param node
     *            the ASTMethodDeclaration where the local variable name will be
     *            searched
     * @return <code>true</code> if the method declaration contains any local
     *         variable named vn and <code>false</code> in other case
     */
    protected boolean isLocalVariable(String vn, Node node) {
        final List<ASTLocalVariableDeclaration> lvars = node.findDescendantsOfType(ASTLocalVariableDeclaration.class);
        if (lvars != null) {
            for (ASTLocalVariableDeclaration lvd : lvars) {
                final ASTVariableDeclaratorId vid = lvd.getFirstDescendantOfType(ASTVariableDeclaratorId.class);
                if (vid != null && vid.hasImageEqualTo(vn)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Gets the image of the ASTName node found by
     * {@link Node#getFirstDescendantOfType(Class)}
     * if it is the greatgrandchild of the given node.
     *
     * E.g.
     * <pre>
     * n = Expression || StatementExpression
     *       PrimaryExpression
     *         PrimaryPrefix
     *           Name
     * </pre>
     *
     * @param n
     *            the node to search
     * @return the image of the first ASTName or <code>null</code>
     */
    protected String getFirstNameImage(Node n) {
        ASTName name = n.getFirstDescendantOfType(ASTName.class);
        if (name != null && name.getNthParent(3) == n) {
            return name.getImage();
        }
        return null;
    }

}
