/*
 * Bytecode Analysis Framework
 * Copyright (C) 2003-2008 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.ba;

import java.util.BitSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.TreeSet;

import org.apache.bcel.generic.ATHROW;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.MethodGen;

import edu.umd.cs.findbugs.ba.Edge.Type;
import edu.umd.cs.findbugs.graph.AbstractGraph;
import edu.umd.cs.findbugs.util.NullIterator;

/**
 * Simple control flow graph abstraction for BCEL.
 *
 * @see BasicBlock
 * @see Edge
 */
public class CFG extends AbstractGraph<Edge, BasicBlock> implements Debug {
    /*
     * ----------------------------------------------------------------------
     * CFG flags
     * ----------------------------------------------------------------------
     */

    /** Flag set if infeasible exception edges have been pruned from the CFG. */
    public static final int PRUNED_INFEASIBLE_EXCEPTIONS = 1;

    /**
     * Flag set if normal return edges from calls to methods which
     * unconditionally throw an exception have been removed.
     */
    public static final int PRUNED_UNCONDITIONAL_THROWERS = 2;

    /**
     * Flag set if CFG has been "refined"; i.e., to the extent possible, all
     * infeasible edges have been removed.
     */
    public static final int REFINED = 4;

    /**
     * Flag set if CFG edges corresponding to failed assertions have been
     * removed.
     */
    public static final int PRUNED_FAILED_ASSERTION_EDGES = 8;

    /** Flag set if CFG is busy (in the process of being refined. */
    public static final int BUSY = 16;

    public static final int FOUND_INEXACT_UNCONDITIONAL_THROWERS = 32;

    /*
     * ----------------------------------------------------------------------
     * Helper classes
     * ----------------------------------------------------------------------
     */

    /**
     * An Iterator over the Locations in the CFG. Because of JSR subroutines,
     * the same instruction may actually be part of multiple basic blocks (with
     * different facts true in each, due to calling context). Locations specify
     * both the instruction and the basic block.
     */
    private class LocationIterator implements Iterator<Location> {
        private final Iterator<BasicBlock> blockIter;

        private BasicBlock curBlock;

        private Iterator<InstructionHandle> instructionIter;

        private Location next;

        private LocationIterator() {
            this.blockIter = blockIterator();
            findNext();
        }

        @Override
        public boolean hasNext() {
            findNext();
            return next != null;
        }

        @Override
        public Location next() {
            findNext();
            if (next == null) {
                throw new NoSuchElementException();
            }
            Location result = next;
            next = null;
            return result;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }

        private void findNext() {
            while (next == null) {
                // Make sure we have an instruction iterator
                if (instructionIter == null) {
                    if (!blockIter.hasNext())
                    {
                        return; // At end
                    }
                    curBlock = blockIter.next();
                    instructionIter = curBlock.instructionIterator();
                }

                if (instructionIter.hasNext()) {
                    next = new Location(instructionIter.next(), curBlock);
                }
                else {
                    instructionIter = null; // Go to next block
                }
            }
        }
    }

    /*
     * ----------------------------------------------------------------------
     * Fields
     * ----------------------------------------------------------------------
     */

    private BasicBlock entry, exit;

    private int flags;

    private String methodName; // for informational purposes

    private MethodGen methodGen;

    private List<Edge> removedEdgeList;

    /*
     * ----------------------------------------------------------------------
     * Public methods
     * ----------------------------------------------------------------------
     */

    /**
     * Constructor. Creates empty control flow graph (with just entry and exit
     * nodes).
     */
    public CFG() {
    }

    /**
     * @param methodName
     *            The methodName to set.
     */
    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public void setMethodGen(MethodGen methodGen) {
        this.methodGen = methodGen;
    }

    public MethodGen getMethodGen() {
        return methodGen;
    }

    /**
     * @return Returns the methodName.
     */
    public String getMethodName() {
        return methodName;
    }

    public String getMethodSig() {
        return methodGen.getSignature();
    }

    public void setFlags(int flags) {
        this.flags = flags;
    }

    public void setFlag(int flags) {
        this.flags |= flags;
    }

    public void clearFlag(int flags) {
        this.flags &= ~flags;
    }

    public int getFlags() {
        return flags;
    }

    public boolean isFlagSet(int flag) {
        return (flags & flag) != 0;
    }

    /**
     * Get the entry node.
     */
    public BasicBlock getEntry() {
        if (entry == null) {
            entry = allocate();
        }
        return entry;
    }

    /**
     * Get the exit node.
     */
    public BasicBlock getExit() {
        if (exit == null) {
            exit = allocate();
        }
        return exit;
    }

    /**
     * Add a unique edge to the graph. There must be no other edge already in
     * the CFG with the same source and destination blocks.
     *
     * @param source
     *            the source basic block
     * @param dest
     *            the destination basic block
     * @param type
     *            the type of edge; see constants in EdgeTypes interface
     * @return the newly created Edge
     * @throws IllegalStateException
     *             if there is already an edge in the CFG with the same source
     *             and destination block
     */
    public Edge createEdge(BasicBlock source, BasicBlock dest, @Edge.Type int type) {
        Edge edge = createEdge(source, dest);
        edge.setType(type);
        return edge;
    }

    /**
     * Look up an Edge by its id.
     *
     * @param id
     *            the id of the edge to look up
     * @return the Edge, or null if no matching Edge was found
     */
    public Edge lookupEdgeById(int id) {
        Iterator<Edge> i = edgeIterator();
        while (i.hasNext()) {
            Edge edge = i.next();
            if (edge.getId() == id) {
                return edge;
            }
        }
        return null;
    }

    /**
     * Look up a BasicBlock by its unique label.
     *
     * @param blockLabel
     *            the label of a BasicBlock
     * @return the BasicBlock with the given label, or null if there is no such
     *         BasicBlock
     */
    public BasicBlock lookupBlockByLabel(int blockLabel) {
        for (Iterator<BasicBlock> i = blockIterator(); i.hasNext();) {
            BasicBlock basicBlock = i.next();
            if (basicBlock.getLabel() == blockLabel) {
                return basicBlock;
            }
        }
        return null;
    }

    /**
     * Get an Iterator over the nodes (BasicBlocks) of the control flow graph.
     */
    public Iterator<BasicBlock> blockIterator() {
        return vertexIterator();
    }

    /*
     * * Get an Iteratable over the nodes (BasicBlocks) of the control flow
     * graph.
     */
    public Iterable<BasicBlock> blocks() {
        return vertices();
    }

    /**
     * Get an Iterator over the Locations in the control flow graph.
     */
    public Iterator<Location> locationIterator() {
        return new LocationIterator();
    }

    /**
     * Get an Iterator over the Locations in the control flow graph.
     */
    public Iterable<Location> locations() {
        return new Iterable<Location>() {
            @Override
            public Iterator<Location> iterator() {
                return locationIterator();
            }
        };
    }

    /**
     * Returns a collection of locations, ordered according to the compareTo
     * ordering over locations. If you want to list all the locations in a CFG
     * for debugging purposes, this is a good order to do so in.
     *
     * @return collection of locations
     */
    public Collection<Location> orderedLocations() {
        TreeSet<Location> tree = new TreeSet<Location>();
        for (Iterator<Location> locs = locationIterator(); locs.hasNext();) {
            Location loc = locs.next();
            tree.add(loc);
        }
        return tree;
    }

    /**
     * Get Collection of basic blocks whose IDs are specified by given BitSet.
     *
     * @param labelSet
     *            BitSet of block labels
     * @return a Collection containing the blocks whose IDs are given
     */
    public Collection<BasicBlock> getBlocks(BitSet labelSet) {
        LinkedList<BasicBlock> result = new LinkedList<BasicBlock>();
        for (Iterator<BasicBlock> i = blockIterator(); i.hasNext();) {
            BasicBlock block = i.next();
            if (labelSet.get(block.getLabel())) {
                result.add(block);
            }
        }
        return result;
    }

    /**
     * Get a Collection of basic blocks which contain the bytecode instruction
     * with given offset.
     *
     * @param offset
     *            the bytecode offset of an instruction
     * @return Collection of BasicBlock objects which contain the instruction
     *         with that offset
     */
    public Collection<BasicBlock> getBlocksContainingInstructionWithOffset(int offset) {
        LinkedList<BasicBlock> result = new LinkedList<BasicBlock>();
        for (Iterator<BasicBlock> i = blockIterator(); i.hasNext();) {
            BasicBlock block = i.next();
            if (block.containsInstructionWithOffset(offset)) {
                result.add(block);
            }
        }
        return result;
    }

    /**
     * Get a Collection of Locations which specify the instruction at given
     * bytecode offset.
     *
     * @param offset
     *            the bytecode offset
     * @return all Locations referring to the instruction at that offset
     */
    public Collection<Location> getLocationsContainingInstructionWithOffset(int offset) {
        LinkedList<Location> result = new LinkedList<Location>();
        for (Iterator<Location> i = locationIterator(); i.hasNext();) {
            Location location = i.next();
            if (location.getHandle().getPosition() == offset) {
                result.add(location);
            }
        }
        return result;
    }

    /**
     * Get the first predecessor reachable from given edge type.
     *
     * @param target
     *            the target block
     * @param edgeType
     *            the edge type leading from the predecessor
     * @return the predecessor, or null if there is no incoming edge with the
     *         specified edge type
     */
    public BasicBlock getPredecessorWithEdgeType(BasicBlock target, @Type int edgeType) {
        Edge edge = getIncomingEdgeWithType(target, edgeType);
        return edge != null ? edge.getSource() : null;
    }

    /**
     * Get the first successor reachable from given edge type.
     *
     * @param source
     *            the source block
     * @param edgeType
     *            the edge type leading to the successor
     * @return the successor, or null if there is no outgoing edge with the
     *         specified edge type
     */
    public BasicBlock getSuccessorWithEdgeType(BasicBlock source, @Type int edgeType) {
        Edge edge = getOutgoingEdgeWithType(source, edgeType);
        return edge != null ? edge.getTarget() : null;
    }

    /**
     * Get the Location where exception(s) thrown on given exception edge are
     * thrown.
     *
     * @param exceptionEdge
     *            the exception Edge
     * @return Location where exception(s) are thrown from
     */
    public Location getExceptionThrowerLocation(Edge exceptionEdge) {
        if (!exceptionEdge.isExceptionEdge()) {
            throw new IllegalArgumentException();
        }

        InstructionHandle handle = exceptionEdge.getSource().getExceptionThrower();
        if (handle == null) {
            throw new IllegalStateException();
        }

        BasicBlock basicBlock = (handle.getInstruction() instanceof ATHROW) ? exceptionEdge.getSource()
                : getSuccessorWithEdgeType(exceptionEdge.getSource(), EdgeTypes.FALL_THROUGH_EDGE);

        if (basicBlock == null) {
            if (removedEdgeList != null) {
                // The fall-through edge might have been removed during
                // CFG pruning. Look for it in the removed edge list.
                for (Edge removedEdge : removedEdgeList) {
                    if (removedEdge.getType() == EdgeTypes.FALL_THROUGH_EDGE
                            && removedEdge.getSource() == exceptionEdge.getSource()) {
                        basicBlock = removedEdge.getTarget();
                        break;
                    }
                }
            }
        }

        if (basicBlock == null) {
            throw new IllegalStateException("No basic block for thrower " + handle + " in " + this.methodGen.getClassName() + "."
                    + this.methodName + " : " + this.methodGen.getSignature());
        }

        return new Location(handle, basicBlock);
    }

    /**
     * Get an Iterator over Edges removed from this CFG.
     *
     * @return Iterator over Edges removed from this CFG
     */
    public Iterator<Edge> removedEdgeIterator() {
        return removedEdgeList != null ? removedEdgeList.iterator() : new NullIterator<Edge>();
    }

    /**
     * Get the first incoming edge in basic block with given type.
     *
     * @param basicBlock
     *            the basic block
     * @param edgeType
     *            the edge type
     * @return the Edge, or null if there is no edge with that edge type
     */
    public Edge getIncomingEdgeWithType(BasicBlock basicBlock, @Type int edgeType) {
        return getEdgeWithType(incomingEdgeIterator(basicBlock), edgeType);
    }

    /**
     * Get the first outgoing edge in basic block with given type.
     *
     * @param basicBlock
     *            the basic block
     * @param edgeType
     *            the edge type
     * @return the Edge, or null if there is no edge with that edge type
     */
    public Edge getOutgoingEdgeWithType(BasicBlock basicBlock, @Type int edgeType) {
        return getEdgeWithType(outgoingEdgeIterator(basicBlock), edgeType);
    }

    private Edge getEdgeWithType(Iterator<Edge> iter,  @Type int edgeType) {
        while (iter.hasNext()) {
            Edge edge = iter.next();
            if (edge.getType() == edgeType) {
                return edge;
            }
        }
        return null;
    }

    /**
     * Allocate a new BasicBlock. The block won't be connected to any node in
     * the graph.
     */
    public BasicBlock allocate() {
        BasicBlock b = new BasicBlock();
        addVertex(b);
        return b;
    }

    /**
     * Get number of basic blocks. This is just here for compatibility with the
     * old CFG method names.
     */
    public int getNumBasicBlocks() {
        return getNumVertices();
    }

    /**
     * Get the number of edge labels allocated. This is just here for
     * compatibility with the old CFG method names.
     */
    public int getMaxEdgeId() {
        return getNumEdgeLabels();
    }

    public void checkIntegrity() {
        // Ensure that basic blocks have only consecutive instructions
        for (Iterator<BasicBlock> i = blockIterator(); i.hasNext();) {
            BasicBlock basicBlock = i.next();
            InstructionHandle prev = null;
            for (Iterator<InstructionHandle> j = basicBlock.instructionIterator(); j.hasNext();) {
                InstructionHandle handle = j.next();
                if (prev != null && prev.getNext() != handle) {
                    throw new IllegalStateException("Non-consecutive instructions in block " + basicBlock.getLabel() + ": prev="
                            + prev + ", handle=" + handle);
                }
                prev = handle;
            }
        }
    }

    @Override
    protected Edge allocateEdge(BasicBlock source, BasicBlock target) {
        return new Edge(source, target);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.umd.cs.findbugs.graph.AbstractGraph#removeEdge(edu.umd.cs.findbugs
     * .graph.AbstractEdge)
     */
    @Override
    public void removeEdge(Edge edge) {
        super.removeEdge(edge);

        // Keep track of removed edges.
        if (removedEdgeList == null) {
            removedEdgeList = new LinkedList<Edge>();
        }
        removedEdgeList.add(edge);
    }

    /**
     * Get number of non-exception control successors of given basic block.
     *
     * @param block
     *            a BasicBlock
     * @return number of non-exception control successors of the basic block
     */
    public int getNumNonExceptionSucessors(BasicBlock block) {
        int numNonExceptionSuccessors = block.getNumNonExceptionSuccessors();
        if (numNonExceptionSuccessors < 0) {
            numNonExceptionSuccessors = 0;
            for (Iterator<Edge> i = outgoingEdgeIterator(block); i.hasNext();) {
                Edge edge = i.next();
                if (!edge.isExceptionEdge()) {
                    numNonExceptionSuccessors++;
                }
            }
            block.setNumNonExceptionSuccessors(numNonExceptionSuccessors);
        }
        return numNonExceptionSuccessors;
    }

    /**
     * Get the Location representing the entry to the CFG. Note that this is a
     * "fake" Location, and shouldn't be relied on to yield source line
     * information.
     *
     * @return Location at entry to CFG
     */
    public Location getLocationAtEntry() {
        InstructionHandle handle = getEntry().getFirstInstruction();
        assert handle != null;
        return new Location(handle, getEntry());
    }

    public Location getPreviousLocation(Location loc) {
        InstructionHandle handle = loc.getHandle();

        BasicBlock basicBlock = loc.getBasicBlock();
        if (basicBlock.getFirstInstruction().equals(handle)) {
            BasicBlock prevBlock = basicBlock;

            while (true) {
                prevBlock = getPredecessorWithEdgeType(prevBlock, EdgeTypes.FALL_THROUGH_EDGE);
                if (prevBlock == null) {
                    return loc;
                }

                handle = prevBlock.getLastInstruction();
                if (handle != null) {
                    return new Location(handle, prevBlock);
                }
            }

        } else {
            handle = handle.getPrev();
            return new Location(handle, basicBlock);

        }

    }

}

