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 one of the 8 Java primitive wrappers. 
 * 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 StickFirstPrimitive extends StickFirstMatches {

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

    private static final String BYTE = "java.lang.Byte";
    private static final String SHORT = "java.lang.Short";
    private static final String INTEGER = "java.lang.Integer";
    private static final String LONG = "java.lang.Long";
    private static final String FLOAT = "java.lang.Float";
    private static final String DOUBLE = "java.lang.Double";
    private static final String CHARACTER = "java.lang.Character";
    private static final String BOOLEAN = "java.lang.Boolean";

    private static final String[] classList = { BYTE, SHORT, INTEGER, LONG,
            FLOAT, DOUBLE, CHARACTER, BOOLEAN };

    /**
     * Create an analysis that tracks methods called on Primitive wrapper classes.
     * 
     * @param sensitivity
     *            depth of the call stack to keep track of
     */
    public StickFirstPrimitive(int sensitivity) {
        super(sensitivity);
    }

    /**
     * Create an analysis that tracks methods called on Primitive wrapper classes 
     * with default sensitivity.
     */
    public StickFirstPrimitive() {
    }

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

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

        }

        return false;
    }

    /**
     * True if calleeClass is one of the 8 primitive wrapper classes
     * 
     * @param calleeClass
     *          Class of the callee for the procedure we are looking at
     * @return True if calleeClass is one of the 8 primitive wrapper classes, 
     *          false otherwise  
     */
    private boolean isPrimitiveWrapper(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 primitive wrapper  "
                        + className);
            }
            if (ts.typeEquals(calleeClass, t)) {
                return true;
            }
        }
        return false;
    }
}
