/*
 * FindBugs - Find bugs in Java programs
 * Copyright (C) 2003,2004 University of Maryland
 *
 * 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 edu.umd.cs.findbugs;

import java.util.HashSet;
import java.util.Iterator;

import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.INVOKESTATIC;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.MONITORENTER;
import org.apache.bcel.generic.MONITOREXIT;
import org.apache.bcel.generic.MethodGen;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import edu.umd.cs.findbugs.ba.BasicBlock;
import edu.umd.cs.findbugs.ba.CFG;
import edu.umd.cs.findbugs.ba.CFGBuilderException;
import edu.umd.cs.findbugs.ba.ClassContext;

/**
 * Build a call graph of the self calls in a class.
 */
public class SelfCalls {
    private static final Logger LOG = LoggerFactory.getLogger(SelfCalls.class);

    private final ClassContext classContext;

    private final CallGraph callGraph;

    private final HashSet<Method> calledMethodSet;

    private boolean hasSynchronization;

    /**
     * Constructor.
     *
     * @param classContext
     *            the ClassContext for the class
     */
    public SelfCalls(ClassContext classContext) {
        this.classContext = classContext;
        this.callGraph = new CallGraph();
        this.calledMethodSet = new HashSet<>();
        this.hasSynchronization = false;
    }

    /**
     * Find the self calls.
     */
    public void execute() throws CFGBuilderException {
        JavaClass jclass = classContext.getJavaClass();
        Method[] methods = jclass.getMethods();

        LOG.debug("Class has {} methods", methods.length);

        // Add call graph nodes for all methods
        for (Method method : methods) {
            callGraph.addNode(method);
        }
        LOG.debug("Added {} nodes to graph", callGraph.getNumVertices());

        // Scan methods for self calls
        for (Method method : methods) {
            MethodGen mg = classContext.getMethodGen(method);
            if (mg == null) {
                continue;
            }

            scan(callGraph.getNodeForMethod(method));
        }

        LOG.debug("Found {} self calls", callGraph.getNumEdges());
    }

    /**
     * Get the self call graph for the class.
     */
    public CallGraph getCallGraph() {
        return callGraph;
    }

    /**
     * Get an Iterator over self-called methods.
     */
    public Iterator<Method> calledMethodIterator() {
        return calledMethodSet.iterator();
    }

    /**
     * Determine whether we are interested in calls for the given method.
     * Subclasses may override. The default version returns true for every
     * method.
     *
     * @param method
     *            the method
     * @return true if we want call sites for the method, false if not
     */
    public boolean wantCallsFor(Method method) {
        return true;
    }

    /**
     * Get an Iterator over all self call sites.
     */
    public Iterator<CallSite> callSiteIterator() {
        return new Iterator<CallSite>() {
            private final Iterator<CallGraphEdge> iter = callGraph.edgeIterator();

            @Override
            public boolean hasNext() {
                return iter.hasNext();
            }

            @Override
            public CallSite next() {
                return iter.next().getCallSite();
            }

            @Override
            public void remove() {
                iter.remove();
            }
        };
    }

    /**
     * Does this class contain any explicit synchronization?
     */
    public boolean hasSynchronization() {
        return hasSynchronization;
    }

    /**
     * Scan a method for self call sites.
     *
     * @param node
     *            the CallGraphNode for the method to be scanned
     */
    private void scan(CallGraphNode node) throws CFGBuilderException {
        Method method = node.getMethod();
        CFG cfg = classContext.getCFG(method);

        if (method.isSynchronized()) {
            hasSynchronization = true;
        }

        Iterator<BasicBlock> i = cfg.blockIterator();
        while (i.hasNext()) {
            BasicBlock block = i.next();
            Iterator<InstructionHandle> j = block.instructionIterator();
            while (j.hasNext()) {
                InstructionHandle handle = j.next();

                Instruction ins = handle.getInstruction();
                if (ins instanceof InvokeInstruction) {
                    InvokeInstruction inv = (InvokeInstruction) ins;
                    Method called = isSelfCall(inv);
                    if (called != null) {
                        // Add edge to call graph
                        CallSite callSite = new CallSite(method, block, handle);
                        callGraph.createEdge(node, callGraph.getNodeForMethod(called), callSite);

                        // Add to called method set
                        calledMethodSet.add(called);
                    }
                } else if (ins instanceof MONITORENTER || ins instanceof MONITOREXIT) {
                    hasSynchronization = true;
                }
            }
        }
    }

    /**
     * Is the given instruction a self-call?
     */
    private Method isSelfCall(InvokeInstruction inv) {
        ConstantPoolGen cpg = classContext.getConstantPoolGen();
        JavaClass jclass = classContext.getJavaClass();

        String calledClassName = inv.getClassName(cpg);

        // FIXME: is it possible we would see a superclass name here?
        // Not a big deal for now, as we are mostly just interested in calls
        // to private methods, for which we will definitely see the right
        // called class name.
        if (!calledClassName.equals(jclass.getClassName())) {
            return null;
        }

        String calledMethodName = inv.getMethodName(cpg);
        String calledMethodSignature = inv.getSignature(cpg);
        boolean isStaticCall = (inv instanceof INVOKESTATIC);

        // Scan methods for one that matches.
        Method[] methods = jclass.getMethods();
        for (Method method : methods) {
            String methodName = method.getName();
            String signature = method.getSignature();
            boolean isStatic = method.isStatic();

            if (methodName.equals(calledMethodName) && signature.equals(calledMethodSignature) && isStatic == isStaticCall) {
                // This method looks like a match.
                return wantCallsFor(method) ? method : null;
            }
        }

        // Hmm...no matching method found.
        // This is almost certainly because the named method
        // was inherited from a superclass.
        LOG.debug("No method found for {}.{} : {}", calledClassName, calledMethodName, calledMethodSignature);
        return null;
    }
}