package serp.bytecode;

import java.io.*;
import java.util.*;

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

/**
 * Attribute declaring the checked exceptions a method can throw.
 *
 * @author Abe White
 */
public class Exceptions extends Attribute {
    private List _indexes = new LinkedList();

    Exceptions(int nameIndex, Attributes owner) {
        super(nameIndex, owner);
    }

    int getLength() {
        return 2 + (2 * _indexes.size());
    }

    /**
     * Return the owning method.
     */
    public BCMethod getMethod() {
        return (BCMethod) getOwner();
    }

    /**
     * Return the indexes in the class {@link ConstantPool} of the
     * {@link ClassEntry}s for the exception types thrown by this method, or
     * an empty array if none.
     */
    public int[] getExceptionIndexes() {
        int[] indexes = new int[_indexes.size()];
        Iterator itr = _indexes.iterator();
        for (int i = 0; i < indexes.length; i++)
            indexes[i] = ((Integer) itr.next()).intValue();
        return indexes;
    }

    /**
     * Set the indexes in the class {@link ConstantPool} of the
     * {@link ClassEntry}s for the exception types thrown by this method. Use
     * null or an empty array for none.
     */
    public void setExceptionIndexes(int[] exceptionIndexes) {
        _indexes.clear();
        if (exceptionIndexes != null)
            for (int i = 0; i < exceptionIndexes.length; i++)
                _indexes.add(Numbers.valueOf(exceptionIndexes[i]));
    }

    /**
     * Return the names of the exception types for this method, or an empty
     * array if none. The names will be in a form suitable for a
     * {@link Class#forName} call.
     */
    public String[] getExceptionNames() {
        String[] names = new String[_indexes.size()];
        Iterator itr = _indexes.iterator();
        int index;
        ClassEntry entry;
        for (int i = 0; i < names.length; i++) {
            index = ((Number) itr.next()).intValue();
            entry = (ClassEntry) getPool().getEntry(index);
            names[i] = getProject().getNameCache().getExternalForm(entry.
                getNameEntry().getValue(), false);
        }
        return names;
    }

    /**
     * Return the {@link Class} objects for the exception types for this
     * method, or an empty array if none.
     */
    public Class[] getExceptionTypes() {
        String[] names = getExceptionNames();
        Class[] types = new Class[names.length];
        for (int i = 0; i < names.length; i++)
            types[i] = Strings.toClass(names[i], getClassLoader());
        return types;
    }

    /**
     * Return bytecode for the exception types of this
     * method, or an empty array if none.
     */
    public BCClass[] getExceptionBCs() {
        String[] names = getExceptionNames();
        BCClass[] types = new BCClass[names.length];
        for (int i = 0; i < names.length; i++)
            types[i] = getProject().loadClass(names[i], getClassLoader());
        return types;
    }

    /**
     * Set the checked exceptions thrown by this method. Use null or an
     * empty array for none.
     */
    public void setExceptions(String[] exceptions) {
        if (exceptions != null) {
            for (int i = 0; i < exceptions.length; i++)
                if (exceptions[i] == null)
                    throw new NullPointerException("exceptions[" + i 
                        + "] = null");
        }

        clear();
        if (exceptions != null)
            for (int i = 0; i < exceptions.length; i++)
                addException(exceptions[i]);
    }

    /**
     * Set the checked exceptions thrown by this method. Use null or an
     * empty array for none.
     */
    public void setExceptions(Class[] exceptions) {
        String[] names = null;
        if (exceptions != null) {
            names = new String[exceptions.length];
            for (int i = 0; i < exceptions.length; i++)
                names[i] = exceptions[i].getName();
        }
        setExceptions(names);
    }

    /**
     * Set the checked exceptions thrown by this method. Use null or an
     * empty array for none.
     */
    public void setExceptions(BCClass[] exceptions) {
        String[] names = null;
        if (exceptions != null) {
            names = new String[exceptions.length];
            for (int i = 0; i < exceptions.length; i++)
                names[i] = exceptions[i].getName();
        }
        setExceptions(names);
    }

    /**
     * Clear this method of all exception declarations.
     */
    public void clear() {
        _indexes.clear();
    }

    /**
     * Remove an exception type thrown by this method.
     *
     * @return true if the method had the exception type, false otherwise
     */
    public boolean removeException(String type) {
        String internalForm = getProject().getNameCache().getInternalForm(type,
            false);
        ClassEntry entry;
        for (Iterator itr = _indexes.iterator(); itr.hasNext();) {
            entry = (ClassEntry) getPool().getEntry(((Integer) itr.next()).
                intValue());
            if (entry.getNameEntry().getValue().equals(internalForm)) {
                itr.remove();
                return true;
            }
        }
        return false;
    }

    /**
     * Remove an exception thrown by this method.
     *
     * @return true if the method had the exception type, false otherwise
     */
    public boolean removeException(Class type) {
        if (type == null)
            return false;
        return removeException(type.getName());
    }

    /**
     * Remove an exception thrown by this method.
     *
     * @return true if the method had the exception type, false otherwise
     */
    public boolean removeException(BCClass type) {
        if (type == null)
            return false;
        return removeException(type.getName());
    }

    /**
     * Add an exception type to those thrown by this method.
     */
    public void addException(String type) {
        int index = getPool().findClassEntry(getProject().getNameCache().
            getInternalForm(type, false), true);
        _indexes.add(Numbers.valueOf(index));
    }

    /**
     * Add an exception to those thrown by this method.
     */
    public void addException(Class type) {
        addException(type.getName());
    }

    /**
     * Add an exception to those thrown by this method.
     */
    public void addException(BCClass type) {
        addException(type.getName());
    }

    /**
     * Return true if the method declares that it throws the given
     * exception type.
     */
    public boolean throwsException(String type) {
        String[] exceptions = getExceptionNames();
        for (int i = 0; i < exceptions.length; i++)
            if (exceptions[i].equals(type))
                return true;
        return false;
    }

    /**
     * Return true if the method declares that it throws the given
     * exception type.
     */
    public boolean throwsException(Class type) {
        if (type == null)
            return false;
        return throwsException(type.getName());
    }

    /**
     * Return true if the method declares that it throws the given
     * exception type.
     */
    public boolean throwsException(BCClass type) {
        if (type == null)
            return false;
        return throwsException(type.getName());
    }

    public void acceptVisit(BCVisitor visit) {
        visit.enterExceptions(this);
        visit.exitExceptions(this);
    }

    void read(Attribute other) {
        setExceptions(((Exceptions) other).getExceptionNames());
    }

    void read(DataInput in, int length) throws IOException {
        _indexes.clear();
        int exceptionCount = in.readUnsignedShort();
        for (int i = 0; i < exceptionCount; i++)
            _indexes.add(Numbers.valueOf((int) in.readUnsignedShort()));
    }

    void write(DataOutput out, int length) throws IOException {
        out.writeShort(_indexes.size());
        for (Iterator itr = _indexes.iterator(); itr.hasNext();)
            out.writeShort(((Number) itr.next()).shortValue());
    }
}
