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

import java.util.List;

import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression;
import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence;
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;

/**
 * Detects and flags the occurrences of specific method calls against an instance of
 * a designated class. I.e. String.indexOf. The goal is to be able to suggest more
 * efficient/modern ways of implementing the same function.
 *
 * Concrete subclasses are expected to provide the name of the target class and an
 * array of method names that we are looking for. We then pass judgment on any literal
 * arguments we find in the subclass as well.
 *
 * @author Brian Remedios
 * @version $Revision$
 */
public abstract class AbstractPoorMethodCall extends AbstractJavaRule {
    //FIXME not sure the abstraction is generic enough to be reused as is.

    /**
     * The name of the type the method will be invoked against.
     * @return String
     */
    protected abstract String targetTypename();

    /**
     * Return the names of all the methods we are scanning for, no brackets or
     * argument types.
     *
     * @return String[]
     */
    protected abstract String[] methodNames();

    /**
     * Returns whether the node being sent to the method is OK or not. Return
     * true if you want to record the method call as a violation.
     *
     * @param arg the node to inspect
     * @return boolean
     */
    protected abstract boolean isViolationArgument(Node arg);

    /**
     * Returns whether the name occurrence is one of the method calls
     * we are interested in.
     *
     * @param occurrence NameOccurrence
     * @return boolean
     */
    private boolean isNotedMethod(NameOccurrence occurrence) {

        if (occurrence == null) {
            return false;
        }

        String methodCall = occurrence.getImage();
        String[] methodNames = methodNames();

        for (String element : methodNames) {
            if (methodCall.indexOf(element) != -1) {
                return true;
            }
        }
        return false;
    }

    /**
     * Method visit.
     * @param node ASTVariableDeclaratorId
     * @param data Object
     * @return Object
     * @see net.sourceforge.pmd.lang.java.ast.JavaParserVisitor#visit(ASTVariableDeclaratorId, Object)
     */
    @Override
    public Object visit(ASTVariableDeclaratorId node, Object data) {
        if (!targetTypename().equals(node.getNameDeclaration().getTypeImage())) {
            return data;
        }

        for (NameOccurrence occ : node.getUsages()) {
            JavaNameOccurrence jocc = (JavaNameOccurrence)occ;
            if (isNotedMethod(jocc.getNameForWhichThisIsAQualifier())) {
                Node parent = jocc.getLocation().jjtGetParent().jjtGetParent();
                if (parent instanceof ASTPrimaryExpression) {
                    // bail out if it's something like indexOf("a" + "b")
                    if (parent.hasDescendantOfType(ASTAdditiveExpression.class)) {
                        return data;
                    }
                    List<ASTLiteral> literals = parent.findDescendantsOfType(ASTLiteral.class);
                    for (int l = 0; l < literals.size(); l++) {
                        ASTLiteral literal = literals.get(l);
                        if (isViolationArgument(literal)) {
                            addViolation(data, jocc.getLocation());
                        }
                    }
                }
            }
        }
        return data;
    }
}
