package serp.bytecode;

import java.util.*;

import serp.bytecode.lowlevel.*;
import serp.util.*;

/**
 * Any typed instruction.
 *
 * @author Abe White
 */
public abstract class TypedInstruction extends Instruction {
    private static final Set _opcodeTypes = new HashSet();
    static {
        _opcodeTypes.add(int.class.getName());
        _opcodeTypes.add(long.class.getName());
        _opcodeTypes.add(float.class.getName());
        _opcodeTypes.add(double.class.getName());
        _opcodeTypes.add(Object.class.getName());
        _opcodeTypes.add(byte.class.getName());
        _opcodeTypes.add(char.class.getName());
        _opcodeTypes.add(short.class.getName());
        _opcodeTypes.add(boolean.class.getName());
        _opcodeTypes.add(void.class.getName());
    }

    TypedInstruction(Code owner) {
        super(owner);
    }

    TypedInstruction(Code owner, int opcode) {
        super(owner, opcode);
    }

    /**
     * Return the type for the given name. Takes into account
     * the given mappings and the demote flag.
     *
     * @param mappings mappings of one type to another; for example,
     * array instruction treat booleans as ints, so
     * to reflect that there should be an index x of the
     * array such that mappings[x][0] = boolean.class and
     * mappings[x][1] = int.class; may be null if no special mappings are needed
     * @param demote if true, all object types will be demoted to Object.class
     */
    String mapType(String type, Class[][] mappings, boolean demote) {
        if (type == null)
            return null;

        type = getProject().getNameCache().getExternalForm(type, false);
        if (!_opcodeTypes.contains(type) && demote)
            type = Object.class.getName();

        if (mappings != null)
            for (int i = 0; i < mappings.length; i++)
                if (mappings[i][0].getName().equals(type))
                    type = mappings[i][1].getName();
        return type;
    }

    /**
     * Return the type name for this instruction.
     * If the type has not been set, this method will return null.
     */
    public abstract String getTypeName();

    /**
     * Return the type for this instruction.
     * If the type has not been set, this method will return null.
     */
    public Class getType() {
        String type = getTypeName();
        if (type == null)
            return null;
        return Strings.toClass(type, getClassLoader());
    }

    /**
     * Return the type for this instruction.
     * If the type has not been set, this method will return null.
     */
    public BCClass getTypeBC() {
        String type = getTypeName();
        if (type == null)
            return null;
        return getProject().loadClass(type, getClassLoader());
    }

    /**
     * Set the type of this instruction. Types that have no direct
     * support will be converted accordingly.
     *
     * @return this instruction, for method chaining
     */
    public abstract TypedInstruction setType(String type);

    /**
     * Set the type of this instruction. Types that have no direct
     * support will be converted accordingly.
     *
     * @return this instruction, for method chaining
     */
    public TypedInstruction setType(Class type) {
        if (type == null)
            return setType((String) null);
        return setType(type.getName());
    }

    /**
     * Set the type of this instruction. Types that have no direct
     * support will be converted accordingly.
     *
     * @return this instruction, for method chaining
     */
    public TypedInstruction setType(BCClass type) {
        if (type == null)
            return setType((String) null);
        return setType(type.getName());
    }
}
