/*
 * Bytecode Analysis Framework
 * 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.ba.bcp;

import java.util.BitSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;

import javax.annotation.Nullable;

import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionHandle;

import edu.umd.cs.findbugs.SystemProperties;
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;
import edu.umd.cs.findbugs.ba.DFSEdgeTypes;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.DepthFirstSearch;
import edu.umd.cs.findbugs.ba.DominatorsAnalysis;
import edu.umd.cs.findbugs.ba.Edge;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.vna.ValueNumberDataflow;
import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame;

/**
 * Match a ByteCodePattern against the code of a method, represented by a CFG.
 * Produces some number of ByteCodePatternMatch objects, which indicate how the
 * pattern matched the bytecode instructions in the method.
 * <p/>
 * <p>
 * This code is a hack and should probably be rewritten.
 *
 * @author David Hovemeyer
 * @see ByteCodePattern
 */
public class PatternMatcher implements DFSEdgeTypes {
    private static final boolean DEBUG = SystemProperties.getBoolean("bcp.debug");

    private static final boolean SHOW_WILD = SystemProperties.getBoolean("bcp.showWild");

    private final ByteCodePattern pattern;

    private final CFG cfg;

    private final ConstantPoolGen cpg;

    private final DepthFirstSearch dfs;

    private final ValueNumberDataflow vnaDataflow;

    private final DominatorsAnalysis domAnalysis;

    private final LinkedList<BasicBlock> workList;

    private final IdentityHashMap<BasicBlock, BasicBlock> visitedBlockMap;

    private final LinkedList<ByteCodePatternMatch> resultList;

    /**
     * Constructor.
     *
     * @param pattern
     *            the ByteCodePattern to look for examples of
     * @param classContext
     *            ClassContext for the class to analyze
     * @param method
     *            the Method to analyze
     */
    public PatternMatcher(ByteCodePattern pattern, ClassContext classContext, Method method) throws CFGBuilderException,
    DataflowAnalysisException {
        this.pattern = pattern;
        this.cfg = classContext.getCFG(method);
        this.cpg = classContext.getConstantPoolGen();
        this.dfs = classContext.getDepthFirstSearch(method);
        this.vnaDataflow = classContext.getValueNumberDataflow(method);
        this.domAnalysis = classContext.getNonExceptionDominatorsAnalysis(method);
        this.workList = new LinkedList<BasicBlock>();
        this.visitedBlockMap = new IdentityHashMap<BasicBlock, BasicBlock>();
        this.resultList = new LinkedList<ByteCodePatternMatch>();
    }

    /**
     * Search for examples of the ByteCodePattern.
     *
     * @return this object
     * @throws DataflowAnalysisException
     *             if the ValueNumberAnalysis did not produce useful values for
     *             the method
     */
    public PatternMatcher execute() throws DataflowAnalysisException {
        workList.addLast(cfg.getEntry());

        while (!workList.isEmpty()) {
            BasicBlock basicBlock = workList.removeLast();
            visitedBlockMap.put(basicBlock, basicBlock);

            // Scan instructions of basic block for possible matches
            BasicBlock.InstructionIterator i = basicBlock.instructionIterator();
            while (i.hasNext()) {
                attemptMatch(basicBlock, i.duplicate());
                i.next();
            }

            // Add successors of the basic block (which haven't been visited
            // already)
            Iterator<BasicBlock> succIterator = cfg.successorIterator(basicBlock);
            while (succIterator.hasNext()) {
                BasicBlock succ = succIterator.next();
                if (visitedBlockMap.get(succ) == null) {
                    workList.addLast(succ);
                }
            }
        }

        return this;
    }

    /**
     * Return an Iterator over the ByteCodePatternMatch objects representing
     * successful matches of the ByteCodePattern.
     */
    public Iterator<ByteCodePatternMatch> byteCodePatternMatchIterator() {
        return resultList.iterator();
    }

    /**
     * Attempt to begin a match.
     *
     * @param basicBlock
     *            the basic block
     * @param instructionIterator
     *            the instruction iterator positioned just before the first
     *            instruction to be matched
     */
    private void attemptMatch(BasicBlock basicBlock, BasicBlock.InstructionIterator instructionIterator)
            throws DataflowAnalysisException {
        work(new State(basicBlock, instructionIterator, pattern.getFirst()));
    }

    // For debugging - number the paths through the CFG
    private int nextPath = 0;

    /**
     * Object representing the current state of the matching algorithm. Provides
     * convenient methods to implement the various steps of the algorithm.
     */
    private class State {
        private final BasicBlock basicBlock;

        private final BasicBlock.InstructionIterator instructionIterator;

        private final PatternElement patternElement;

        private int matchCount;

        private PatternElementMatch currentMatch;

        private BindingSet bindingSet;

        private boolean canFork;

        private final int parentPath;

        private final int path;

        @Override
        public String toString() {
            return patternElement + " : " + instructionIterator + " :: " + currentMatch;
        }

        /**
         * Constructor. Builds the start state.
         *
         * @param basicBlock
         *            the initial basic block
         * @param instructionIterator
         *            the instructionIterator indicating where to start matching
         * @param patternElement
         *            the first PatternElement of the pattern
         */
        public State(BasicBlock basicBlock, BasicBlock.InstructionIterator instructionIterator, PatternElement patternElement) {
            this(null, basicBlock, instructionIterator, patternElement, 0, null, null, true);
        }

        /**
         * Constructor.
         */
        public State(@Nullable State parent, BasicBlock basicBlock, BasicBlock.InstructionIterator instructionIterator,
                PatternElement patternElement, int matchCount, @Nullable PatternElementMatch currentMatch,
                @Nullable BindingSet bindingSet, boolean canFork) {
            this.basicBlock = basicBlock;
            this.instructionIterator = instructionIterator;
            this.patternElement = patternElement;
            this.matchCount = matchCount;
            this.currentMatch = currentMatch;
            this.bindingSet = bindingSet;
            this.canFork = canFork;
            this.parentPath = (parent != null) ? parent.path : -1;
            this.path = nextPath++;
        }

        /**
         * Make an exact copy of this object.
         */
        public State duplicate() {
            return new State(this, basicBlock, instructionIterator, patternElement, matchCount, currentMatch, bindingSet, canFork);
        }

        /**
         * Get basic block.
         */
        public BasicBlock getBasicBlock() {
            return basicBlock;
        }

        /**
         * Get current pattern element.
         */
        public PatternElement getPatternElement() {
            return patternElement;
        }

        /**
         * Get current pattern element match.
         */
        public PatternElementMatch getCurrentMatch() {
            return currentMatch;
        }

        /**
         * Determine if the match is complete.
         */
        public boolean isComplete() {
            // We're done when we reach the end of the chain
            // of pattern elements.
            return patternElement == null;
        }

        /**
         * Get a ByteCodePatternMatch representing the complete match.
         */
        public ByteCodePatternMatch getResult() {
            if (!isComplete()) {
                throw new IllegalStateException("match not complete!");
            }
            return new ByteCodePatternMatch(bindingSet, currentMatch);
        }

        /**
         * Try to produce a new state that will finish matching the current
         * element and start matching the next element. Returns null if the
         * current element is not complete.
         */
        public State advanceToNextElement() {
            if (!canFork || matchCount < patternElement.minOccur()) {
                // Current element is not complete, or we already
                // forked at this point
                return null;
            }

            // Create state to advance to matching next pattern element
            // at current basic block and instruction.
            State advance = new State(this, basicBlock, instructionIterator.duplicate(), patternElement.getNext(), 0,
                    currentMatch, bindingSet, true);

            // Now that this state has forked from this element
            // at this instruction, it must not do so again.
            this.canFork = false;

            return advance;
        }

        /**
         * Determine if the current pattern element can continue to match
         * instructions.
         */
        public boolean currentElementCanContinue() {
            return matchCount < patternElement.maxOccur();
        }

        /**
         * Determine if there are more instructions in the same basic block.
         */
        public boolean moreInstructionsInBasicBlock() {
            return instructionIterator.hasNext();
        }

        /**
         * Match current pattern element with next instruction in basic block.
         * Returns MatchResult if match succeeds, null otherwise.
         */
        public MatchResult matchNextInBasicBlock() throws DataflowAnalysisException {
            if (!moreInstructionsInBasicBlock()) {
                throw new IllegalStateException("At end of BB!");
            }

            // Move to location of next instruction to be matched
            Location location = new Location(instructionIterator.next(), basicBlock);
            return matchLocation(location);
        }

        /**
         * Determine if it is possible to continue matching in a successor basic
         * block.
         */
        public boolean canAdvanceToNextBasicBlock() {
            return currentMatch == null || currentMatch.allowTrailingEdges();
        }

        /**
         * Get most recently matched instruction.
         */
        public InstructionHandle getLastMatchedInstruction() {
            if (currentMatch == null) {
                throw new IllegalStateException("no current match!");
            }
            return currentMatch.getMatchedInstructionInstructionHandle();
        }

        /**
         * Return a new State for continuing the overall pattern match in a
         * successor basic block.
         *
         * @param edge
         *            the Edge leading to the successor basic block
         * @param matchResult
         *            a MatchResult representing the match of the last
         *            instruction in the predecessor block; null if none
         */
        public State advanceToSuccessor(Edge edge, MatchResult matchResult) {
            // If we have just matched an instruction, then we allow the
            // matching PatternElement to choose which edges are acceptable.
            // This allows PatternElements to select particular control edges;
            // for example, only continue the pattern on the true branch
            // of an "if" comparison.
            if (matchResult != null && !matchResult.getPatternElement().acceptBranch(edge, getLastMatchedInstruction())) {
                return null;
            }

            return new State(this, edge.getTarget(), edge.getTarget().instructionIterator(), patternElement, matchCount,
                    currentMatch, bindingSet, canFork);
        }

        /**
         * Determine if we need to look for a dominated instruction at this
         * point in the search.
         */
        public boolean lookForDominatedInstruction() {
            return patternElement.getDominatedBy() != null && matchCount == 0;
        }

        /**
         * Return Iterator over states representing dominated instructions that
         * continue the match.
         */
        public Iterable<State> dominatedInstructionStateIterable() throws DataflowAnalysisException {
            if (!lookForDominatedInstruction()) {
                throw new IllegalStateException();
            }
            LinkedList<State> stateList = new LinkedList<State>();

            State dup = this.duplicate();

            if (currentMatch != null) {
                // Find the referenced instruction.
                PatternElementMatch dominator = currentMatch.getFirstLabeledMatch(patternElement.getDominatedBy());
                BasicBlock domBlock = dominator.getBasicBlock();
                InstructionHandle domInstruction = dominator.getMatchedInstructionInstructionHandle();
                // Find all basic blocks dominated by the dominator block.
                for (Iterator<BasicBlock> i = cfg.blockIterator(); i.hasNext();) {
                    BasicBlock block = i.next();
                    boolean includeInstructions = block != domBlock;

                    BitSet dominators = domAnalysis.getResultFact(block);
                    if (block == domBlock || dominators.get(domBlock.getLabel())) {
                        // This block is dominated by the dominator block.
                        // Each instruction in the block which matches the
                        // current pattern
                        // element is a new state continuing the match.
                        for (Iterator<InstructionHandle> j = block.instructionIterator(); j.hasNext();) {
                            InstructionHandle next = j.next();
                            if (includeInstructions) {
                                MatchResult matchResult = dup.matchLocation(new Location(next, block));
                                if (matchResult != null) {
                                    stateList.add(dup);
                                    dup = this.duplicate();
                                }
                            } else if (next.equals(domInstruction)) {
                                includeInstructions = true;
                            }
                        }
                    }
                }
            }

            return stateList;
        }

        private MatchResult matchLocation(Location location) throws DataflowAnalysisException {
            // Get the ValueNumberFrames before and after the instruction
            ValueNumberFrame before = vnaDataflow.getFactAtLocation(location);
            ValueNumberFrame after = vnaDataflow.getFactAfterLocation(location);

            // Try to match the instruction against the pattern element.
            boolean debug = DEBUG && (!(patternElement instanceof Wild) || SHOW_WILD);
            if (debug) {

                debug((parentPath >= 0 ? parentPath + "->" : "") + path + ": Match " + patternElement + " against "
                        + location.getHandle() + " " + (bindingSet != null ? bindingSet.toString() : "[]") + "...");
            }
            MatchResult matchResult = patternElement.match(location.getHandle(), cpg, before, after, bindingSet);
            if (debug) {
                debug("\t" + ((matchResult != null) ? " ==> MATCH" : " ==> NOT A MATCH"));
            }
            if (matchResult != null) {
                // Successful match!
                // Update state to reflect that the match has occurred.
                ++matchCount;
                canFork = true;
                currentMatch = new PatternElementMatch(matchResult.getPatternElement(), location.getHandle(),
                        location.getBasicBlock(), matchCount, currentMatch);
                bindingSet = matchResult.getBindingSet();
            }
            return matchResult;
        }
    }

    private void debug(String s) {
        if (!DEBUG) {
            throw new IllegalStateException("Only call if DEBUG is true");
        }
        System.out.print("                                            ".substring(0, depth));
        System.out.println(s);
    }

    /**
     * Match a sequence of pattern elements, starting at the given one. The
     * InstructionIterator should generally be positioned just before the next
     * instruction to be matched. However, it may be positioned at the end of a
     * basic block, in which case nothing will happen except that we will try to
     * continue the match in the non-backedge successors of the basic block.
     */

    int depth = 0;

    private void work(State state) throws DataflowAnalysisException {
        depth++;
        try {
            // Have we reached the end of the pattern?
            if (state.isComplete()) {
                // This is a complete match.
                if (DEBUG) {
                    debug("FINISHED A MATCH!");
                }
                resultList.add(state.getResult());
                return;
            }

            if (DEBUG) {
                debug("Matching " + state.getPatternElement() + " against " + state.currentMatch);
            }
            // If we've reached the minimum number of occurrences for this
            // pattern element, we can advance to the next pattern element
            // without trying
            // to match this instruction again. We make sure that we only
            // advance to
            // the next element once for this matchCount.
            State advance = state.advanceToNextElement();
            if (advance != null) {
                work(advance);
            }

            // If we've reached the maximum number of occurrences for this
            // pattern element, then we can't continue.
            if (!state.currentElementCanContinue()) {
                return;
            }

            MatchResult matchResult = null;

            // Are we looking for an instruction dominated by an earlier
            // matched instruction?
            if (state.lookForDominatedInstruction()) {
                Iterable<State> dominatedInstructions = state.dominatedInstructionStateIterable();
                for (State s : dominatedInstructions) {
                    if (DEBUG) {
                        debug("trying " + s);
                    }
                    work(s);
                }
                return;
            }

            // Is there another instruction in this basic block?
            if (state.moreInstructionsInBasicBlock()) {
                // Try to match it.
                matchResult = state.matchNextInBasicBlock();
                if (matchResult == null) {
                    return;
                }
            }

            // Continue the match at each successor instruction,
            // using the same PatternElement.
            if (state.moreInstructionsInBasicBlock()) {
                // Easy case; continue matching in the same basic block.
                work(state);
            } else if (state.canAdvanceToNextBasicBlock()) {
                // We've reached the end of the basic block.
                // Try to advance to the successors of this basic block,
                // ignoring loop backedges.
                Iterator<Edge> i = cfg.outgoingEdgeIterator(state.getBasicBlock());
                BitSet visitedSuccessorSet = new BitSet();
                while (i.hasNext()) {
                    Edge edge = i.next();
                    if (dfs.getDFSEdgeType(edge) == BACK_EDGE) {
                        continue;
                    }

                    BasicBlock destBlock = edge.getTarget();
                    int destId = destBlock.getLabel();

                    // CFGs can have duplicate edges
                    if (visitedSuccessorSet.get(destId)) {
                        continue;
                    }
                    visitedSuccessorSet.set(destId, true);

                    // See if we can continue matching in the successor basic
                    // block.
                    State succState = state.advanceToSuccessor(edge, matchResult);
                    if (succState != null) {
                        work(succState);
                    }
                }
            }
        } finally {
            depth--;
        }

    }
}

