/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2003-2007 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.jsr305;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;

import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.AbstractDataflowAnalysis;
import edu.umd.cs.findbugs.ba.BasicBlock;
import edu.umd.cs.findbugs.ba.CFG;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.Edge;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.XFactory;
import edu.umd.cs.findbugs.ba.XMethod;
import edu.umd.cs.findbugs.ba.vna.ValueNumber;
import edu.umd.cs.findbugs.ba.vna.ValueNumberDataflow;
import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame;
import edu.umd.cs.findbugs.util.ClassName;

/**
 * Abstract base class for type qualifier dataflow analyses.
 *
 * @author David Hovemeyer
 */
public abstract class TypeQualifierDataflowAnalysis extends AbstractDataflowAnalysis<TypeQualifierValueSet> {

    static String primitiveType (String simpleClass) {
        if (simpleClass.equals("Integer"))
            return "int";
        return  simpleClass.toLowerCase();
    }

    static boolean isIdentifyFunctionForTypeQualifiers(XMethod m) {
        String className = m.getClassName();
        if (!className.startsWith("java.lang"))
            return false;
        String methodName = m.getName();

        if (m.isStatic()) {
            if (!methodName.equals("valueOf"))
                return false;
            String signature = m.getSignature();
            if (signature.charAt(2) != ')')
                return false;
        } else {
            String simpleClassName = ClassName.extractSimpleName(className);

            if (!methodName.equals(primitiveType(simpleClassName) + "Value"))
                return false;
            String signature = m.getSignature();
            if (signature.charAt(1) != ')')
                return false;
        }
        return true;
    }


    static final boolean DEBUG_VERBOSE = SystemProperties.getBoolean("ctq.dataflow.debug.verbose");

    protected final XMethod xmethod;

    protected final CFG cfg;

    protected final ValueNumberDataflow vnaDataflow;

    protected final TypeQualifierValue typeQualifierValue;

    protected final ConstantPoolGen cpg;

    private final Map<Location, Set<SourceSinkInfo>> sourceSinkMap;

    /**
     * Constructor.
     *
     * @param xmethod
     *            XMethod object containing information about the method being
     *            analyzed
     * @param cfg
     *            the control-flow graph (CFG) of the method being analyzed
     * @param vnaDataflow
     *            ValueNumberDataflow for the method
     * @param cpg
     *            the constant pool being built up
     * @param typeQualifierValue
     *            the TypeQualifierValue we want the dataflow analysis to check
     */
    public TypeQualifierDataflowAnalysis(XMethod xmethod, CFG cfg, ValueNumberDataflow vnaDataflow, ConstantPoolGen cpg,
            TypeQualifierValue typeQualifierValue) {
        this.xmethod = xmethod;
        this.cfg = cfg;
        this.vnaDataflow = vnaDataflow;
        this.cpg = cpg;
        this.typeQualifierValue = typeQualifierValue;
        this.sourceSinkMap = new HashMap<Location, Set<SourceSinkInfo>>();
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.umd.cs.findbugs.ba.DataflowAnalysis#initEntryFact(java.lang.Object)
     */
    public void initEntryFact(TypeQualifierValueSet result) throws DataflowAnalysisException {
        result.makeValid();
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.umd.cs.findbugs.ba.AbstractDataflowAnalysis#isFactValid(java.lang
     * .Object)
     */
    @Override
    public boolean isFactValid(TypeQualifierValueSet fact) {
        return fact.isValid();
    }

    /*
     * (non-Javadoc)
     *
     * @see edu.umd.cs.findbugs.ba.DataflowAnalysis#copy(java.lang.Object,
     * java.lang.Object)
     */
    public void copy(TypeQualifierValueSet source, TypeQualifierValueSet dest) {
        dest.makeSameAs(source);
    }

    /*
     * (non-Javadoc)
     *
     * @see edu.umd.cs.findbugs.ba.DataflowAnalysis#createFact()
     */
    public TypeQualifierValueSet createFact() {
        return new TypeQualifierValueSet(typeQualifierValue);
    }

    /*
     * (non-Javadoc)
     *
     * @see edu.umd.cs.findbugs.ba.DataflowAnalysis#isTop(java.lang.Object)
     */
    public boolean isTop(TypeQualifierValueSet fact) {
        return fact.isTop();
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.umd.cs.findbugs.ba.DataflowAnalysis#makeFactTop(java.lang.Object)
     */
    public void makeFactTop(TypeQualifierValueSet fact) {
        fact.setTop();
    }

    /*
     * (non-Javadoc)
     *
     * @see edu.umd.cs.findbugs.ba.DataflowAnalysis#meetInto(java.lang.Object,
     * edu.umd.cs.findbugs.ba.Edge, java.lang.Object)
     */
    public void meetInto(TypeQualifierValueSet fact, Edge edge, TypeQualifierValueSet result) throws DataflowAnalysisException {
        if (fact.isTop() || result.isBottom()) {
            // result does not change
            return;
        } else if (fact.isBottom() || result.isTop()) {
            result.makeSameAs(fact);
            return;
        }

        assert fact.isValid();
        assert result.isValid();

        result.mergeWith(fact);
    }

    /*
     * (non-Javadoc)
     *
     * @see edu.umd.cs.findbugs.ba.DataflowAnalysis#same(java.lang.Object,
     * java.lang.Object)
     */
    public boolean same(TypeQualifierValueSet fact1, TypeQualifierValueSet fact2) {
        return fact1.equals(fact2);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.umd.cs.findbugs.ba.BasicAbstractDataflowAnalysis#edgeTransfer(edu
     * .umd.cs.findbugs.ba.Edge, java.lang.Object)
     */
    @Override
    public void edgeTransfer(Edge edge, TypeQualifierValueSet fact) throws DataflowAnalysisException {
        if (!fact.isValid()) {
            return;
        }

        // Propagate flow values and source information across phi nodes.

        ValueNumberFrame targetVnaFrame = vnaDataflow.getStartFact(edge.getTarget());
        ValueNumberFrame sourceVnaFrame = vnaDataflow.getResultFact(edge.getSource());

        if (!targetVnaFrame.isValid() || !sourceVnaFrame.isValid()) {
            return;
        }

        // The source and target frames can have different numbers of slots
        // if the target is an exception handler.
        // So, merge the minimum number of slots in either frame.
        int numSlotsToMerge = Math.min(sourceVnaFrame.getNumSlots(), targetVnaFrame.getNumSlots());

        for (int i = 0; i < numSlotsToMerge; i++) {
            ValueNumber targetVN = targetVnaFrame.getValue(i);
            ValueNumber sourceVN = sourceVnaFrame.getValue(i);

            if (!targetVN.equals(sourceVN) && targetVN.hasFlag(ValueNumber.PHI_NODE)) {
                // targetVN is a phi result
                if (DEBUG_VERBOSE) {
                    System.out.println("Phi node: " + fact.valueNumberToString(sourceVN) + " -> "
                            + fact.valueNumberToString(targetVN));
                }
                propagateAcrossPhiNode(fact, sourceVN, targetVN);
                if (DEBUG_VERBOSE) {
                    String dir = isForwards() ? "forwards" : "backwards";
                    System.out.println("After propagating phi node " + dir + ": " + fact.toString());
                }
            }
        }
    }

    protected abstract void propagateAcrossPhiNode(TypeQualifierValueSet fact, ValueNumber sourceVN, ValueNumber targetVN);

    /**
     * This method must be called before the dataflow analysis is executed.
     *
     * @throws DataflowAnalysisException
     */
    public abstract void registerSourceSinkLocations() throws DataflowAnalysisException;

    protected void registerSourceSink(SourceSinkInfo sourceSinkInfo) {
        Set<SourceSinkInfo> set = sourceSinkMap.get(sourceSinkInfo.getLocation());
        if (set == null) {
            set = new HashSet<SourceSinkInfo>();
            sourceSinkMap.put(sourceSinkInfo.getLocation(), set);
        }
        set.add(sourceSinkInfo);
    }

    /**
     * Get the set of SourceSinkInfo objects representing sources/sinks at a
     * given Location.
     *
     * @param location
     *            a Location
     * @return Set of SourceSinkInfo objects representing sources/sinks at the
     *         Location
     */
    public Set<SourceSinkInfo> getSourceSinkInfoSet(Location location) {
        Set<SourceSinkInfo> result = sourceSinkMap.get(location);
        return result != null ? result : Collections.<SourceSinkInfo> emptySet();
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * edu.umd.cs.findbugs.ba.AbstractDataflowAnalysis#transferInstruction(org
     * .apache.bcel.generic.InstructionHandle,
     * edu.umd.cs.findbugs.ba.BasicBlock, java.lang.Object)
     */
    @Override
    public void transferInstruction(InstructionHandle handle, BasicBlock basicBlock, TypeQualifierValueSet fact)
            throws DataflowAnalysisException {
        if (!fact.isValid()) {
            return;
        }
        Location location = new Location(handle, basicBlock);

        Instruction i = handle.getInstruction();
        if (i instanceof InvokeInstruction) {
            InvokeInstruction ii = (InvokeInstruction) i;
            XMethod m = XFactory.createXMethod(ii, cpg);
            if (TypeQualifierDataflowAnalysis.isIdentifyFunctionForTypeQualifiers(m)) {
                ValueNumberFrame vnaFrameAtLocation = vnaDataflow.getFactAtLocation(location);
                ValueNumberFrame vnaFrameAfterInstruction = vnaDataflow.getFactAfterLocation(location);
                ValueNumber in = vnaFrameAtLocation.getStackValue(0);
                ValueNumber out = vnaFrameAfterInstruction.getStackValue(0);
                fact.copyInfo(in, out);
                return;
            }
        }

        // This is a simple process.
        // Check to see if there are any sources/sinks at this location,
        // and if so, model them.

        Set<SourceSinkInfo> sourceSinkSet = sourceSinkMap.get(location);
        if (sourceSinkSet != null) {
            if (DEBUG_VERBOSE) {
                System.out.println("Modeling source/sink at " + location.toCompactString() + ": " + sourceSinkSet.toString());
            }
            for (SourceSinkInfo sourceSinkInfo : sourceSinkSet) {
                fact.modelSourceSink(sourceSinkInfo);
            }
        }
    }
}
