/**
 * 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.java.ast.ASTAllocationExpression;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration;

/**
 * Implementation note: this rule currently ignores return types of y.x.z,
 * currently it handles only local type fields.
 * Created on Jan 17, 2005
 *
 * @author mgriffa
 */
public class MethodReturnsInternalArrayRule extends AbstractSunSecureRule {

    @Override
    public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
        if (node.isInterface()) {
            return data;
        }
        return super.visit(node, data);
    }

    @Override
    public Object visit(ASTMethodDeclaration method, Object data) {
        if (!method.getResultType().returnsArray() || method.isPrivate()) {
            return data;
        }
        List<ASTReturnStatement> returns = method.findDescendantsOfType(ASTReturnStatement.class);
        ASTTypeDeclaration td = method.getFirstParentOfType(ASTTypeDeclaration.class);
        for (ASTReturnStatement ret: returns) {
            final String vn = getReturnedVariableName(ret);
            if (!isField(vn, td)) {
                continue;
            }
            if (ret.findDescendantsOfType(ASTPrimarySuffix.class).size() > 2) {
                continue;
            }
            if (ret.hasDescendantOfType(ASTAllocationExpression.class)) {
                continue;
            }
            if (hasArraysCopyOf(ret)) {
                continue;
            }
            if (hasClone(ret, vn)) {
                continue;
            }
            if (!isLocalVariable(vn, method)) {
                addViolation(data, ret, vn);
            } else {
                // This is to handle field hiding
                final ASTPrimaryPrefix pp = ret.getFirstDescendantOfType(ASTPrimaryPrefix.class);
                if (pp != null && pp.usesThisModifier()) {
                    final ASTPrimarySuffix ps = ret.getFirstDescendantOfType(ASTPrimarySuffix.class);
                    if (ps.hasImageEqualTo(vn)) {
                        addViolation(data, ret, vn);
                    }
                }
            }
        }
        return data;
    }

    private boolean hasClone(ASTReturnStatement ret, String varName) {
        List<ASTPrimaryExpression> expressions = ret.findDescendantsOfType(ASTPrimaryExpression.class);
        for (ASTPrimaryExpression e : expressions) {
            if (e.jjtGetChild(0) instanceof ASTPrimaryPrefix
                    && e.jjtGetNumChildren() == 2
                    && e.jjtGetChild(1) instanceof ASTPrimarySuffix
                    && ((ASTPrimarySuffix) e.jjtGetChild(1)).isArguments()
                    && ((ASTPrimarySuffix) e.jjtGetChild(1)).getArgumentCount() == 0) {
                ASTName name = e.getFirstDescendantOfType(ASTName.class);
                if (name != null && name.hasImageEqualTo(varName + ".clone")) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean hasArraysCopyOf(ASTReturnStatement ret) {
        List<ASTPrimaryExpression> expressions = ret.findDescendantsOfType(ASTPrimaryExpression.class);
        for (ASTPrimaryExpression e : expressions) {
            if (e.jjtGetNumChildren() == 2 && e.jjtGetChild(0) instanceof ASTPrimaryPrefix
                    && e.jjtGetChild(0).jjtGetNumChildren() == 1 && e.jjtGetChild(0).jjtGetChild(0) instanceof ASTName
                    && e.jjtGetChild(0).jjtGetChild(0).getImage().endsWith("Arrays.copyOf")) {
                return true;
            }
        }
        return false;
    }
}
