package accrue.analysis.pointer.analyses;

import polyglot.types.ClassType;
import polyglot.types.MemberInstance;
import polyglot.types.SemanticException;
import polyglot.types.Type;
import polyglot.types.TypeSystem;
import polyglot.util.InternalCompilerError;
import accrue.analysis.pointer.CContext;
import accrue.analysis.pointer.CallSiteNode;
import accrue.analysis.pointer.HContext;

/**
 * Precisely track the first calls into the Java collections framework. Nested
 * calls up to the sensitivity will "stick" in the context, further calls will 
 * be dropped.
 * <p>
 * This should be used together with a more precise analysis context using 
 * {@link CrossProductAnalysis}
 */
public class StickFirstCollections extends StickFirstMatches {

    /**
     * Name for the analysis
     */
    private static final String NAME = "CollectionsStickFirst";

    private static final String MAP = "java.util.Map";
    private static final String SET = "java.util.Set";
    private static final String LIST = "java.util.List";
    private static final String QUEUE = "java.util.Queue";
    private static final String DEQUE = "java.util.Deque";

    /**
     * Array of Strings containing class names for the interfaces in the 
     * Java collections framework
     */
    private static final String[] classList = { MAP, SET, LIST, QUEUE, DEQUE };

    /**
     * Create a new {@link HeapAbstractionFactory} with the given sensitivity
     * 
     * @param sensitivity 
     *          how many call sites into the collections framework
     *          we want to keep
     */
    public StickFirstCollections(int sensitivity) {
        super(sensitivity);

    }

    /**
     * Create a new {@link HeapAbstractionFactory} with the default sensitivity
     */
    public StickFirstCollections() {
    }

    @Override
    boolean match(CallSiteNode callSite, HContext receiver,
            CContext callerContext) {
        if (callSite.callee() instanceof MemberInstance) {
            MemberInstance mi = (MemberInstance) callSite.callee();
            if (mi.container().isClass()) {
                return isContainerSubtype(mi.container().toClass());
            }

        }

        return false;
    }

    /**
     * True if calleeClass is a subtype of one of the Java container interfaces
     *
     * @param calleeClass
     *          Class of the callee for the procedure we are looking at
     * @return True if calleeClass is a subtype of one of the Java container 
     *          interfaces, false otherwise  
     */
    private boolean isContainerSubtype(ClassType calleeClass) {
        TypeSystem ts = calleeClass.typeSystem();
        for (String className : classList) {
            Type t;
            try {
                t = ts.typeForName(className);
            }
            catch (SemanticException e) {
                throw new InternalCompilerError("Cannot find type for container "
                        + className);
            }
            if (ts.isSubtype(calleeClass, t)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public String toString() {
        return NAME;
    }
}
