package serp.bytecode;

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

import serp.bytecode.lowlevel.*;

/**
 * A member field or method of a class.
 *
 * @author Abe White
 */
public abstract class BCMember extends Annotated {
    private BCClass _owner = null;
    private int _access = Constants.ACCESS_PRIVATE;
    private int _nameIndex = 0;
    private int _descriptorIndex = 0;
    private Collection _attrs = new LinkedList();

    BCMember(BCClass owner) {
        _owner = owner;
    }

    /**
     * Return the {@link BCClass} that declares this member.
     */
    public BCClass getDeclarer() {
        return _owner;
    }

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

    /**
     * Return the access flags for this member as a bit array of
     * ACCESS_XXX constants from {@link Constants}. This can be used to
     * transfer access flags between members without getting/setting each
     * possible access flag. Defaults to {@link Constants#ACCESS_PRIVATE}
     */
    public int getAccessFlags() {
        return _access;
    }

    /**
     * Set the access flags for this member as a bit array of
     * ACCESS_XXX constants from {@link Constants}. This can be used to
     * transfer access flags between members without getting/setting each
     * possible access flag. Defaults to {@link Constants#ACCESS_PRIVATE}
     */
    public void setAccessFlags(int access) {
        _access = access;
    }

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

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

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

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

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

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

    /**
     * Manipulate the member access flags.
     */
    public boolean isPackage() {
        boolean hasAccess = false;
        hasAccess |= (getAccessFlags() & Constants.ACCESS_PRIVATE) > 0;
        hasAccess |= (getAccessFlags() & Constants.ACCESS_PROTECTED) > 0;
        hasAccess |= (getAccessFlags() & Constants.ACCESS_PUBLIC) > 0;
        return !hasAccess;
    }

    /**
     * Manipulate the member access flags.
     */
    public void makePackage() {
        setAccessFlags(getAccessFlags() & ~Constants.ACCESS_PUBLIC);
        setAccessFlags(getAccessFlags() & ~Constants.ACCESS_PRIVATE);
        setAccessFlags(getAccessFlags() & ~Constants.ACCESS_PROTECTED);
    }

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

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

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

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

    /**
     * Manipulate the field access flags.  This method also checks the synthetic
     * member attribute.
     */
    public boolean isSynthetic() {
        return (getAccessFlags() & Constants.ACCESS_SYNTHETIC) > 0
            || getAttribute(Constants.ATTR_SYNTHETIC) != null;
    }

    /**
     * Manipulate the field access flags.  This method also manipulates the
     * synthetic member attribute.
     */
    public void setSynthetic(boolean on) {
        if (on) {
            setAccessFlags(getAccessFlags() | Constants.ACCESS_SYNTHETIC);
            addAttribute(Constants.ATTR_SYNTHETIC);
        } else {
            setAccessFlags(getAccessFlags() & ~Constants.ACCESS_SYNTHETIC);
            removeAttribute(Constants.ATTR_SYNTHETIC);
        }
    }

    /////////////////////////
    // Descriptor operations
    /////////////////////////

    /**
     * Return the index in the class {@link ConstantPool} of the
     * {@link UTF8Entry} holding the name of this member.
     */
    public int getNameIndex() {
        return _nameIndex;
    }

    /**
     * Set the index in the class {@link ConstantPool} of the
     * {@link UTF8Entry} holding the name of this member.
     */
    public void setNameIndex(int index) {
        String origName = getName();
        _nameIndex = index;
        // change all the references in the owning class
        setEntry(origName, getDescriptor());
    }

    /**
     * Return the index in the class {@link ConstantPool} of the
     * {@link UTF8Entry} holding the descriptor of this member.
     */
    public int getDescriptorIndex() {
        return _descriptorIndex;
    }

    /**
     * Set the index in the class {@link ConstantPool} of the
     * {@link UTF8Entry} holding the descriptor of this member.
     */
    public void setDescriptorIndex(int index) {
        String origDesc = getDescriptor();
        _descriptorIndex = index;
        // change all the references in the owning class
        setEntry(getName(), origDesc);
    }

    /**
     * Return the name of this member.
     */
    public String getName() {
        return ((UTF8Entry) getPool().getEntry(_nameIndex)).getValue();
    }

    /**
     * Set the name of this member.
     */
    public void setName(String name) {
        String origName = getName();
        // reset the name
        _nameIndex = getPool().findUTF8Entry(name, true);
        // change all references in the owning class
        setEntry(origName, getDescriptor());
    }

    /**
     * Return the descriptor of this member, in internal form.
     */
    public String getDescriptor() {
        return ((UTF8Entry) getPool().getEntry(_descriptorIndex)).getValue();
    }

    /**
     * Set the descriptor of this member.
     */
    public void setDescriptor(String desc) {
        String origDesc = getDescriptor();
        // reset the desc
        desc = getProject().getNameCache().getInternalForm(desc, true);
        _descriptorIndex = getPool().findUTF8Entry(desc, true);
        // change all the references in the owning class
        setEntry(getName(), origDesc);
    }

    /**
     * Resets the {@link ComplexEntry} of the owning class corresponding to
     * this member. Changes in the member will therefore propogate to all
     * code in the class.
     */
    private void setEntry(String origName, String origDesc) {
        // find the entry matching this member, if any
        String owner = getProject().getNameCache().getInternalForm
            (_owner.getName(), false);
        ConstantPool pool = getPool();
        int index;
        if (this instanceof BCField)
            index = pool.findFieldEntry(origName, origDesc, owner, false);
        else if (!_owner.isInterface())
            index = pool.findMethodEntry(origName, origDesc, owner, false);
        else
            index = pool.findInterfaceMethodEntry(origName, origDesc, owner,
                false);

        // change the entry to match the new info; this is dones so
        // that refs to the member in code will still be valid after the 
        // change, without changing any other constants that happened to match
        // the old name and/or descriptor
        if (index != 0) {
            ComplexEntry complex = (ComplexEntry) pool.getEntry(index);
            int ntIndex = pool.findNameAndTypeEntry(getName(), getDescriptor(),
                true);
            complex.setNameAndTypeIndex(ntIndex);
        }
    }

    ///////////////////////
    // Convenience methods
    ///////////////////////

    /**
     * Convenience method to return deprecation information for the member.
     * Acts internally through the {@link Attributes} interface.
     */
    public boolean isDeprecated() {
        return getAttribute(Constants.ATTR_DEPRECATED) != null;
    }

    /**
     * Convenience method to set whether this member should be considered
     * deprecated. Acts internally through the {@link Attributes} interface.
     */
    public void setDeprecated(boolean on) {
        if (!on)
            removeAttribute(Constants.ATTR_DEPRECATED);
        else if (!isDeprecated())
            addAttribute(Constants.ATTR_DEPRECATED);
    }

    ////////////////////////////////
    // Implementation of Attributes
    ////////////////////////////////

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

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

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

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

    Collection getAttributesHolder() {
        return _attrs;
    }

    /**
     * Either this method or {@link #read} must be called prior to use
     * of this class. The given descriptor must be in internal form.
     */
    void initialize(String name, String descriptor) {
        _nameIndex = getPool().findUTF8Entry(name, true);
        _descriptorIndex = getPool().findUTF8Entry(descriptor, true);
    }

    ///////////////////////////////
    // Implementation of Annotated
    ///////////////////////////////

    BCClass getBCClass() {
        return _owner;
    }

    /**
     * Used when this member is deleted from its class.
     */
    void invalidate() {
        _owner = null;
    }

    void read(DataInput in) throws IOException {
        _access = in.readUnsignedShort();
        _nameIndex = in.readUnsignedShort();
        _descriptorIndex = in.readUnsignedShort();
        readAttributes(in);
    }

    void write(DataOutput out) throws IOException {
        out.writeShort(_access);
        out.writeShort(_nameIndex);
        out.writeShort(_descriptorIndex);
        writeAttributes(out);
    }
}
