package serp.bytecode;

import java.io.*;

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

/**
 * Any referenced class that is not a package member is represented by
 * this structure. This includes member classes and interfaces.
 *
 * @author Abe White
 */
public class InnerClass implements BCEntity, VisitAcceptor {
    private int _index = 0;
    private int _nameIndex = 0;
    private int _ownerIndex = 0;
    private int _access = Constants.ACCESS_PRIVATE;
    private InnerClasses _owner = null;

    InnerClass(InnerClasses owner) {
        _owner = owner;
    }

    /**
     * Inner classes are stored in an {@link InnerClasses} attribute.
     */
    public InnerClasses getOwner() {
        return _owner;
    }

    void invalidate() {
        _owner = null;
    }

    /////////////////////
    // Access operations
    /////////////////////

    /**
     * Return the access flags of the inner class.
     */
    public int getAccessFlags() {
        return _access;
    }

    /**
     * Set the access flags of the inner class.
     */
    public void setAccessFlags(int accessFlags) {
        _access = accessFlags;
    }

    /**
     * Manipulate the inner class access flags.
     */
    public boolean isPublic() {
        return (getAccessFlags() & Constants.ACCESS_PUBLIC) > 0;
    }

    /**
     * Manipulate the inner class access flags.
     */
    public void makePublic() {
        setAccessFlags(getAccessFlags() | Constants.ACCESS_PUBLIC);
        setAccessFlags(getAccessFlags() & ~Constants.ACCESS_PRIVATE);
        setAccessFlags(getAccessFlags() & ~Constants.ACCESS_PROTECTED);
    }

    /**
     * Manipulate the inner class access flags.
     */
    public boolean isProtected() {
        return (getAccessFlags() & Constants.ACCESS_PROTECTED) > 0;
    }

    /**
     * Manipulate the inner class access flags.
     */
    public void makeProtected() {
        setAccessFlags(getAccessFlags() & ~Constants.ACCESS_PUBLIC);
        setAccessFlags(getAccessFlags() & ~Constants.ACCESS_PRIVATE);
        setAccessFlags(getAccessFlags() | Constants.ACCESS_PROTECTED);
    }

    /**
     * Manipulate the inner class access flags.
     */
    public boolean isPrivate() {
        return (getAccessFlags() & Constants.ACCESS_PRIVATE) > 0;
    }

    /**
     * Manipulate the inner class access flags.
     */
    public void makePrivate() {
        setAccessFlags(getAccessFlags() & ~Constants.ACCESS_PUBLIC);
        setAccessFlags(getAccessFlags() | Constants.ACCESS_PRIVATE);
        setAccessFlags(getAccessFlags() & ~Constants.ACCESS_PROTECTED);
    }

    /**
     * Manipulate the inner class access flags.
     */
    public boolean isFinal() {
        return (getAccessFlags() & Constants.ACCESS_FINAL) > 0;
    }

    /**
     * Manipulate the inner class access flags.
     */
    public void setFinal(boolean on) {
        if (on)
            setAccessFlags(getAccessFlags() | Constants.ACCESS_FINAL);
        else
            setAccessFlags(getAccessFlags() & ~Constants.ACCESS_FINAL);
    }

    /**
     * Manipulate the inner class access flags.
     */
    public boolean isStatic() {
        return (getAccessFlags() & Constants.ACCESS_STATIC) > 0;
    }

    /**
     * Manipulate the inner class access flags.
     */
    public void setStatic(boolean on) {
        if (on)
            setAccessFlags(getAccessFlags() | Constants.ACCESS_STATIC);
        else
            setAccessFlags(getAccessFlags() & ~Constants.ACCESS_STATIC);
    }

    /**
     * Manipulate the class access flags.
     */
    public boolean isInterface() {
        return (getAccessFlags() & Constants.ACCESS_INTERFACE) > 0;
    }

    /**
     * Manipulate the class access flags.
     */
    public void setInterface(boolean on) {
        if (on) {
            setAccessFlags(getAccessFlags() | Constants.ACCESS_INTERFACE);
            setAbstract(true);
        } else
            setAccessFlags(getAccessFlags() & ~Constants.ACCESS_INTERFACE);
    }

    /**
     * Manipulate the class access flags.
     */
    public boolean isAbstract() {
        return (getAccessFlags() & Constants.ACCESS_ABSTRACT) > 0;
    }

    /**
     * Manipulate the class access flags.
     */
    public void setAbstract(boolean on) {
        if (on)
            setAccessFlags(getAccessFlags() | Constants.ACCESS_INTERFACE);
        else
            setAccessFlags(getAccessFlags() & ~Constants.ACCESS_INTERFACE);
    }

    /**
     * Manipulate the inner class access flags.
     */
    public boolean isSynthetic() {
        return (getAccessFlags() & Constants.ACCESS_SYNTHETIC) > 0;
    }

    /**
     * Manipulate the inner class access flags.
     */
    public void setSynthetic(boolean on) {
        if (on)
            setAccessFlags(getAccessFlags() | Constants.ACCESS_SYNTHETIC);
        else
            setAccessFlags(getAccessFlags() & ~Constants.ACCESS_SYNTHETIC);
    }

    /**
     * Manipulate the inner class access flags.
     */
    public boolean isAnnotation() {
        return (getAccessFlags() & Constants.ACCESS_ANNOTATION) > 0;
    }

    /**
     * Manipulate the inner class access flags.  Setting to true also makes this
     * an interface.
     */
    public void setAnnotation(boolean on) {
        if (on) {
            setAccessFlags(getAccessFlags() | Constants.ACCESS_ANNOTATION);
            setAccessFlags(getAccessFlags() | Constants.ACCESS_INTERFACE);
        } else
            setAccessFlags(getAccessFlags() & ~Constants.ACCESS_ANNOTATION);
    }

    /**
     * Manipulate the inner class access flags.
     */
    public boolean isEnum() {
        return (getAccessFlags() & Constants.ACCESS_ENUM) > 0;
    }

    /**
     * Manipulate the inner class access flags.
     */
    public void setEnum(boolean on) {
        if (on)
            setAccessFlags(getAccessFlags() | Constants.ACCESS_ENUM);
        else
            setAccessFlags(getAccessFlags() & ~Constants.ACCESS_ENUM);
    }

    ////////////////////////////////
    // Name, type, owner operations
    ////////////////////////////////

    /**
     * Return the {@link ConstantPool} index of the {@link UTF8Entry} that
     * describes the simple name this class is referred to in source, or
     * 0 for anonymous classes.
     */
    public int getNameIndex() {
        return _nameIndex;
    }

    /**
     * Set the {@link ConstantPool} index of the {@link UTF8Entry} that
     * describes the simple name this class is referred to in source, or
     * 0 for anonymous classes.
     */
    public void setNameIndex(int nameIndex) {
        _nameIndex = nameIndex;
    }

    /**
     * Return the simple name of this inner class, or null if anonymous.
     */
    public String getName() {
        if (getNameIndex() == 0)
            return null;
        return ((UTF8Entry) getPool().getEntry(getNameIndex())).getValue();
    }

    /**
     * Set the simple name of this inner class.
     */
    public void setName(String name) {
        if (name == null)
            setNameIndex(0);
        else
            setNameIndex(getPool().findUTF8Entry(name, true));
    }

    /**
     * Return the {@link ConstantPool} index of the {@link ClassEntry} that
     * describes this class, or 0 if none.
     */
    public int getTypeIndex() {
        return _index;
    }

    /**
     * Set the {@link ConstantPool} index of the {@link ClassEntry} that
     * describes this class.
     */
    public void setTypeIndex(int index) {
        _index = index;
    }

    /**
     * Return the full name of the inner class, or null if unset.
     */
    public String getTypeName() {
        if (getTypeIndex() == 0)
            return null;
        ClassEntry entry = (ClassEntry) getPool().getEntry(getTypeIndex());
        return getProject().getNameCache().getExternalForm(entry.getNameEntry().
            getValue(), false);
    }

    /**
     * Return the type of the inner class.
     * 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 inner class.
     */
    public void setType(String type) {
        if (type == null)
            setTypeIndex(0);
        else {
            type = getProject().getNameCache().getInternalForm(type, false);
            setTypeIndex(getPool().findClassEntry(type, true));
        }
    }

    /**
     * Set the type of this inner class.
     */
    public void setType(Class type) {
        if (type == null)
            setType((String) null);
        else
            setType(type.getName());
    }

    /**
     * Set the type of this inner class.
     */
    public void setType(BCClass type) {
        if (type == null)
            setType((String) null);
        else
            setType(type.getName());
    }

    /**
     * Return the {@link ConstantPool} index of the {@link ClassEntry} that
     * describes the declaring class, or 0 if this class is not a member class.
     */
    public int getDeclarerIndex() {
        return _ownerIndex;
    }

    /**
     * Set the {@link ConstantPool} index of the {@link ClassEntry} that
     * describes the declaring class, or 0 if this class is not a member class.
     */
    public void setDeclarerIndex(int ownerIndex) {
        _ownerIndex = ownerIndex;
    }

    /**
     * Return the full name of the declaring class, or null if unset/not a
     * member.
     */
    public String getDeclarerName() {
        if (getDeclarerIndex() == 0)
            return null;
        ClassEntry entry = (ClassEntry) getPool().getEntry(getDeclarerIndex());
        return getProject().getNameCache().getExternalForm(entry.getNameEntry().
            getValue(), false);
    }

    /**
     * Return the type of the declaring class.
     * If the type has not been set or the class is not a member, this method
     * will return null.
     */
    public Class getDeclarerType() {
        String type = getDeclarerName();
        if (type == null)
            return null;
        return Strings.toClass(type, getClassLoader());
    }

    /**
     * Return the type for this instruction.
     * If the type has not been set or the class is not a member, this method
     * will return null.
     */
    public BCClass getDeclarerBC() {
        String type = getDeclarerName();
        if (type == null)
            return null;
        return getProject().loadClass(type, getClassLoader());
    }

    /**
     * Set the type of this declaring class.
     */
    public void setDeclarer(String type) {
        if (type == null)
            setDeclarerIndex(0);
        else {
            type = getProject().getNameCache().getInternalForm(type, false);
            setDeclarerIndex(getPool().findClassEntry(type, true));
        }
    }

    /**
     * Set the type of this declaring class.
     */
    public void setDeclarer(Class type) {
        if (type == null)
            setDeclarer((String) null);
        else
            setDeclarer(type.getName());
    }

    /**
     * Set the type of this declaring class.
     */
    public void setDeclarer(BCClass type) {
        if (type == null)
            setDeclarer((String) null);
        else
            setDeclarer(type.getName());
    }

    ///////////////////////////
    // BCEntity implementation
    ///////////////////////////

    public Project getProject() {
        return _owner.getProject();
    }

    public ConstantPool getPool() {
        return _owner.getPool();
    }

    public ClassLoader getClassLoader() {
        return _owner.getClassLoader();
    }

    public boolean isValid() {
        return _owner != null;
    }

    public void acceptVisit(BCVisitor visit) {
        visit.enterInnerClass(this);
        visit.exitInnerClass(this);
    }

    //////////////////
    // I/O operations
    //////////////////

    void read(DataInput in) throws IOException {
        setTypeIndex(in.readUnsignedShort());
        setDeclarerIndex(in.readUnsignedShort());
        setNameIndex(in.readUnsignedShort());
        setAccessFlags(in.readUnsignedShort());
    }

    void write(DataOutput out) throws IOException {
        out.writeShort(getTypeIndex());
        out.writeShort(getDeclarerIndex());
        out.writeShort(getNameIndex());
        out.writeShort(getAccessFlags());
    }
}
