public class Annotation {

    /** Member value is represented by a ConstantIntegerInfo */
    public static final char MEMBER_TAG_BOOLEAN = 'Z';
    
    /** Member value is represented by a ConstantIntegerInfo */
    public static final char MEMBER_TAG_BYTE = 'B';
    
    /** Member value is represented by a ConstantIntegerInfo */
    public static final char MEMBER_TAG_SHORT = 'S';
    
    /** Member value is represented by a ConstantIntegerInfo */
    public static final char MEMBER_TAG_CHAR = 'C';
    
    /** Member value is represented by a ConstantIntegerInfo */
    public static final char MEMBER_TAG_INT = 'I';
    
    /** Member value is represented by a ConstantLongInfo */
    public static final char MEMBER_TAG_LONG = 'J';
        
    /** Member value is represented by a ConstantFloatInfo */
    public static final char MEMBER_TAG_FLOAT = 'F';

    /** Member value is represented by a ConstantDoubleInfo */
    public static final char MEMBER_TAG_DOUBLE = 'D';

    /** Member value is represented by a ConstantUTFInfo */
    public static final char MEMBER_TAG_STRING = 's';

    /** Member value is represented by a ConstantClassInfo */
    public static final char MEMBER_TAG_CLASS = 'c';

    /** Member value is represented by an EnumConstValue */
    public static final char MEMBER_TAG_ENUM = 'e';

    /** Member value is represented by a MemberValue array */
    public static final char MEMBER_TAG_ARRAY = '[';

    /** Member value is represented by an Annotation */
    public static final char MEMBER_TAG_ANNOTATION = '@';

    private final ConstantPool mCp;
    private ConstantUTFInfo mType;
    private final Map<String, MemberValue> mMemberValues;
    
    public Annotation(ConstantPool cp) {
        mCp = cp;
        mMemberValues = new LinkedHashMap<String, MemberValue>(2);
    }
    
    public Annotation(ConstantPool cp, DataInput din) throws IOException {
        mCp = cp;
        mType = (ConstantUTFInfo)cp.getConstant(din.readUnsignedShort());

        int memberCount = din.readUnsignedShort();
        mMemberValues = new LinkedHashMap<String, MemberValue>(memberCount);
        
        for (int i=0; i<memberCount; i++) {
            String name = ((ConstantUTFInfo)cp.getConstant(din.readUnsignedShort())).getValue();
            mMemberValues.put(name, new MemberValue(cp, din));
        }
    }
    
    public ConstantUTFInfo getTypeConstant() {
        return mType;
    }

    public TypeDesc getType() {
        return TypeDesc.forDescriptor(mType.getValue());
    }
    
    public void setTypeConstant(ConstantUTFInfo type) {
        mType = type;
    }
    
    public void setType(TypeDesc type) {
        setTypeConstant(mCp.addConstantUTF(type.getDescriptor()));
    }

    /**
     * Returns an unmodifiable map of member names (String) to MemberValue
     * objects.
     */
    public Map<String, MemberValue> getMemberValues() {
        return Collections.unmodifiableMap(mMemberValues);
    }
    
    public void putMemberValue(String name, MemberValue mv) {
        mCp.addConstantUTF(name);
        mMemberValues.put(name, mv);
    }

    public void putMemberValue(String name, boolean value) {
        mCp.addConstantUTF(name);
        mMemberValues.put(name, makeMemberValue(value));
    }

    public void putMemberValue(String name, byte value) {
        mCp.addConstantUTF(name);
        mMemberValues.put(name, makeMemberValue(value));
    }
    
    public void putMemberValue(String name, short value) {
        mCp.addConstantUTF(name);
        mMemberValues.put(name, makeMemberValue(value));
    }

    public void putMemberValue(String name, char value) {
        mCp.addConstantUTF(name);
        mMemberValues.put(name, makeMemberValue(value));
    }

    public void putMemberValue(String name, int value) {
        mCp.addConstantUTF(name);
        mMemberValues.put(name, makeMemberValue(value));
    }

    public void putMemberValue(String name, long value) {
        mCp.addConstantUTF(name);
        mMemberValues.put(name, makeMemberValue(value));
    }

    public void putMemberValue(String name, float value) {
        mCp.addConstantUTF(name);
        mMemberValues.put(name, makeMemberValue(value));
    }

    public void putMemberValue(String name, double value) {
        mCp.addConstantUTF(name);
        mMemberValues.put(name, makeMemberValue(value));
    }

    public void putMemberValue(String name, String value) {
        mCp.addConstantUTF(name);
        mMemberValues.put(name, makeMemberValue(value));
    }

    public void putMemberValue(String name, TypeDesc value) {
        mCp.addConstantUTF(name);
        mMemberValues.put(name, makeMemberValue(value));
    }

    public void putMemberValue(String name, MemberValue[] value) {
        mCp.addConstantUTF(name);
        mMemberValues.put(name, makeMemberValue(value));
    }

    public void putMemberValue(String name, TypeDesc enumType, String enumName) {
        mCp.addConstantUTF(name);
        mMemberValues.put(name, makeMemberValue(enumType, enumName));
    }

    /**
     * @see #makeAnnotation
     */
    public void putMemberValue(String name, Annotation value) {
        mCp.addConstantUTF(name);
        mMemberValues.put(name, makeMemberValue(value));
    }

    public MemberValue makeMemberValue(boolean value) {
        return new MemberValue(MEMBER_TAG_BOOLEAN, mCp.addConstantInteger(value ? 1 : 0));
    }

    public MemberValue makeMemberValue(byte value) {
        return new MemberValue(MEMBER_TAG_BYTE, mCp.addConstantInteger(value));
    }
    
    public MemberValue makeMemberValue(short value) {
        return new MemberValue(MEMBER_TAG_SHORT, mCp.addConstantInteger(value));
    }

    public MemberValue makeMemberValue(char value) {
        return new MemberValue(MEMBER_TAG_CHAR, mCp.addConstantInteger(value));
    }
    
    public MemberValue makeMemberValue(int value) {
        return new MemberValue(MEMBER_TAG_INT, mCp.addConstantInteger(value));
    }
    
    public MemberValue makeMemberValue(long value) {
        return new MemberValue(MEMBER_TAG_LONG, mCp.addConstantLong(value));
    }
    
    public MemberValue makeMemberValue(float value) {
        return new MemberValue(MEMBER_TAG_FLOAT, mCp.addConstantFloat(value));
    }
    
    public MemberValue makeMemberValue(double value) {
        return new MemberValue(MEMBER_TAG_DOUBLE, mCp.addConstantDouble(value));
    }
    
    public MemberValue makeMemberValue(String value) {
        return new MemberValue(MEMBER_TAG_STRING, mCp.addConstantUTF(value));
    }

    public MemberValue makeMemberValue(TypeDesc value) {
        return new MemberValue(MEMBER_TAG_CLASS, mCp.addConstantUTF(value.getDescriptor()));
    }
    
    public MemberValue makeMemberValue(TypeDesc enumType, String enumName) {
        return new MemberValue(MEMBER_TAG_ENUM,
                               new EnumConstValue(mCp.addConstantUTF(enumType.getDescriptor()),
                                                  mCp.addConstantUTF(enumName)));
    }

    public MemberValue makeMemberValue(MemberValue[] value) {
        return new MemberValue(MEMBER_TAG_ARRAY, value);
    }

    /**
     * @see #makeAnnotation
     */
    public MemberValue makeMemberValue(Annotation value) {
        return new MemberValue(MEMBER_TAG_ANNOTATION, value);
    }

    public Annotation makeAnnotation() {
        return new Annotation(mCp);
    }

    public int getLength() {
        int length = 4;
        for (MemberValue mv : mMemberValues.values()) {
            length += 2 + mv.getLength();
        }
        return length;
    }
    
    public void writeTo(DataOutput dout) throws IOException {
        dout.writeShort(mType.getIndex());
        int memberCount = mMemberValues.size();
        dout.writeShort(memberCount);
        for (Map.Entry<String, MemberValue> entry : mMemberValues.entrySet()) {
            dout.writeShort(mCp.addConstantUTF(entry.getKey()).getIndex());
            entry.getValue().writeTo(dout);
        }
    }

    public static class MemberValue {
        private final char mTag;
        private final Object mValue;
        
        public MemberValue(char tag, Object value) {
            switch (mTag = tag) {
            default:
                throw new IllegalArgumentException
                    ("Illegal annotation member value tag: " + mTag);
                
            case MEMBER_TAG_BOOLEAN:
            case MEMBER_TAG_BYTE:
            case MEMBER_TAG_SHORT:
            case MEMBER_TAG_CHAR:
            case MEMBER_TAG_INT:
                if (value instanceof ConstantIntegerInfo) {
                    mValue = value;
                } else {
                    throw new IllegalArgumentException("Value must be ConstantIntegerInfo");
                }
                break;

            case MEMBER_TAG_LONG:
                if (value instanceof ConstantLongInfo) {
                    mValue = value;
                } else {
                    throw new IllegalArgumentException("Value must be ConstantLongInfo");
                }
                break;

            case MEMBER_TAG_FLOAT:
                if (value instanceof ConstantFloatInfo) {
                    mValue = value;
                } else {
                    throw new IllegalArgumentException("Value must be ConstantFloatInfo");
                }
                break;

            case MEMBER_TAG_DOUBLE:
                if (value instanceof ConstantDoubleInfo) {
                    mValue = value;
                } else {
                    throw new IllegalArgumentException("Value must be ConstantDoubleInfo");
                }
                break;

            case MEMBER_TAG_CLASS:
                if (value instanceof ConstantUTFInfo) {
                    mValue = value;
                } else {
                    throw new IllegalArgumentException("Value must be ConstantUTFInfo");
                }
                break;

            case MEMBER_TAG_STRING:
                if (value instanceof ConstantUTFInfo) {
                    mValue = value;
                } else {
                    throw new IllegalArgumentException("Value must be ConstantUTFInfo");
                }
                break;
                
            case MEMBER_TAG_ENUM:
                if (value instanceof EnumConstValue) {
                    mValue = value;
                } else {
                    throw new IllegalArgumentException("Value must be EnumConstValue");
                }
                break;
                
            case MEMBER_TAG_ARRAY:
                if (value instanceof MemberValue[]) {
                    mValue = value;
                } else {
                    throw new IllegalArgumentException("Value must be MemberValue[]");
                }
                break;
                
            case MEMBER_TAG_ANNOTATION:
                if (value instanceof Annotation) {
                    mValue = value;
                } else {
                    throw new IllegalArgumentException("Value must be Annotation");
                }
                break;
            }
        }
        
        public MemberValue(ConstantPool cp, DataInput din) throws IOException {
            switch (mTag = (char)din.readUnsignedByte()) {
            default:
                throw new IllegalStateException
                    ("Illegal annotation member value tag: " + mTag);
                
            case MEMBER_TAG_BOOLEAN:
            case MEMBER_TAG_BYTE:
            case MEMBER_TAG_SHORT:
            case MEMBER_TAG_CHAR:
            case MEMBER_TAG_INT:
            case MEMBER_TAG_LONG:
            case MEMBER_TAG_FLOAT:
            case MEMBER_TAG_DOUBLE:
            case MEMBER_TAG_CLASS:
            case MEMBER_TAG_STRING:
                mValue = cp.getConstant(din.readUnsignedShort());
                break;
                
            case MEMBER_TAG_ENUM:
                mValue = new EnumConstValue(cp, din);
                break;
                
            case MEMBER_TAG_ARRAY:
                int length = din.readUnsignedShort();
                MemberValue[] values = new MemberValue[length];
                for (int i=0; i<length; i++) {
                    values[i] = new MemberValue(cp, din);
                }
                mValue = values;
                break;
                
            case MEMBER_TAG_ANNOTATION:
                mValue = new Annotation(cp, din);
                break;
            }
        }
        
        public char getTag() {
            return mTag;
        }
        
        public Object getValue() {
            return mValue;
        }
        
        public int getLength() {
            switch (mTag) {
            default:
                return 1;

            case MEMBER_TAG_BOOLEAN:
            case MEMBER_TAG_BYTE:
            case MEMBER_TAG_SHORT:
            case MEMBER_TAG_CHAR:
            case MEMBER_TAG_INT:
            case MEMBER_TAG_LONG:
            case MEMBER_TAG_FLOAT:
            case MEMBER_TAG_DOUBLE:
            case MEMBER_TAG_CLASS:
            case MEMBER_TAG_STRING:
                return 3;
                
            case MEMBER_TAG_ENUM:
                return 1 + ((EnumConstValue)mValue).getLength();
                
            case MEMBER_TAG_ARRAY: {
                MemberValue[] values = (MemberValue[])mValue;
                int length = 3;
                for (int i=0; i<values.length; i++) {
                    length += values[i].getLength();
                }
                return length;
            }
                
            case MEMBER_TAG_ANNOTATION:
                return 1 + ((Annotation)mValue).getLength();
            }
        }
        
        public void writeTo(DataOutput dout) throws IOException {
            dout.writeByte(mTag);

            switch (mTag) {
            case MEMBER_TAG_BOOLEAN:
            case MEMBER_TAG_BYTE:
            case MEMBER_TAG_SHORT:
            case MEMBER_TAG_CHAR:
            case MEMBER_TAG_INT:
            case MEMBER_TAG_LONG:
            case MEMBER_TAG_FLOAT:
            case MEMBER_TAG_DOUBLE:
            case MEMBER_TAG_CLASS:
            case MEMBER_TAG_STRING:
                dout.writeShort(((ConstantInfo)mValue).getIndex());
                break;
                
            case MEMBER_TAG_ENUM:
                ((EnumConstValue)mValue).writeTo(dout);
                break;
                
            case MEMBER_TAG_ARRAY:
                MemberValue[] values = (MemberValue[])mValue;
                dout.writeShort(values.length);
                for (int i=0; i<values.length; i++) {
                    values[i].writeTo(dout);
                }
                break;
                
            case MEMBER_TAG_ANNOTATION:
                ((Annotation)mValue).writeTo(dout);
                break;
            }
        }
    }
    
    public static class EnumConstValue {
        private final ConstantUTFInfo mTypeName;
        private final ConstantUTFInfo mConstName;

        public EnumConstValue(ConstantUTFInfo typeName, ConstantUTFInfo constName) {
            mTypeName = typeName;
            mConstName = constName;
        }

        public EnumConstValue(ConstantPool cp, DataInput din) throws IOException {
            mTypeName = (ConstantUTFInfo)cp.getConstant(din.readUnsignedShort());
            mConstName = (ConstantUTFInfo)cp.getConstant(din.readUnsignedShort());
        }

        public ConstantUTFInfo getTypeName() {
            return mTypeName;
        }

        public ConstantUTFInfo getConstName() {
            return mConstName;
        }

        public int getLength() {
            return 4;
        }
        
        public void writeTo(DataOutput dout) throws IOException {
            dout.writeShort(mTypeName.getIndex());
            dout.writeShort(mConstName.getIndex());
        }
    }
} public class CodeAttr extends Attribute {

    private CodeBuffer mCodeBuffer;
    private List<Attribute> mAttributes = new ArrayList<Attribute>(2);
    
    private LineNumberTableAttr mLineNumberTable;
    private LocalVariableTableAttr mLocalVariableTable;

    private LineNumberTableAttr mOldLineNumberTable;
    private LocalVariableTableAttr mOldLocalVariableTable;

    private StackMapTableAttr mOldStackMapTable;
    private StackMapTableAttr mStackMapTable;

    public CodeAttr(ConstantPool cp) {
        super(cp, CODE);
    }

    public CodeAttr(ConstantPool cp, String name) {
        super(cp, name);
    }
    
    public CodeAttr(ConstantPool cp, String name, int length,
                    DataInput din, AttributeFactory attrFactory)
        throws IOException
    {
        super(cp, name);

        final int maxStackDepth = din.readUnsignedShort();
        final int maxLocals = din.readUnsignedShort();

        final byte[] byteCodes = new byte[din.readInt()];
        din.readFully(byteCodes);

        int exceptionHandlerCount = din.readUnsignedShort();
        final ExceptionHandler[] handlers = 
            new ExceptionHandler[exceptionHandlerCount];

        for (int i=0; i<exceptionHandlerCount; i++) {
            handlers[i] = ExceptionHandler.readFrom(cp, din);
        }
        
        mCodeBuffer = new CodeBuffer() {
            public int getMaxStackDepth() {
                return maxStackDepth;
            }
            
            public int getMaxLocals() {
                return maxLocals;
            }
            
            public byte[] getByteCodes() {
                return byteCodes.clone();
            }
            
            public ExceptionHandler[] getExceptionHandlers() {
                return handlers.clone();
            }
        };

        int attributeCount = din.readUnsignedShort();
        for (int i=0; i<attributeCount; i++) {
            addAttribute(Attribute.readFrom(cp, din, attrFactory));
        }
    }

    /**
     * Returns null if no CodeBuffer is defined for this CodeAttr.
     */
    public CodeBuffer getCodeBuffer() {
        return mCodeBuffer;
    }

    /**
     * As a side effect of calling this method, new line number and local
     * variable tables are created.
     */
    public void setCodeBuffer(CodeBuffer code) {
        mCodeBuffer = code;
        mOldLineNumberTable = mLineNumberTable;
        mOldLocalVariableTable = mLocalVariableTable;
        mOldStackMapTable = mStackMapTable;
        mAttributes.remove(mLineNumberTable);
        mAttributes.remove(mLocalVariableTable);
        mAttributes.remove(mStackMapTable);
        mLineNumberTable = null;
        mLocalVariableTable = null;
        mStackMapTable = null;
    }
    
    /**
     * Returns the line number in the source code from the given bytecode
     * address (start_pc).
     *
     * @return -1 if no line number is mapped for the start_pc.
     */
    public int getLineNumber(Location start) {
        LineNumberTableAttr table = mOldLineNumberTable;
        if (table == null) {
            table = mLineNumberTable;
        }

        if (table == null || start.getLocation() < 0) {
            return -1;
        } else {
            return table.getLineNumber(start);
        }
    }

    /**
     * Returns local variable info at the given location, for the given number.
     *
     * @return null if unknown
     */
    public LocalVariable getLocalVariable(Location useLocation, int number) {
        int useLoc = useLocation.getLocation();
        if (useLoc < 0) {
            return null;
        } else {
            return getLocalVariable(useLoc, number);
        }
    }

    /**
     * Returns local variable info at the given location, for the given number.
     *
     * @return null if unknown
     */
    public LocalVariable getLocalVariable(int useLocation, int number) {
        LocalVariableTableAttr table = mOldLocalVariableTable;
        if (table == null) {
            table = mLocalVariableTable;
        }

        if (table == null) {
            return null;
        } else {
            return table.getLocalVariable(useLocation, number);
        }
    }

    /**
     * Map a bytecode address (start_pc) to a line number in the source code
     * as a debugging aid.
     */
    public void mapLineNumber(Location start, int line_number) {
        if (mLineNumberTable == null) {
            addAttribute(new LineNumberTableAttr(getConstantPool()));
        }
        mLineNumberTable.addEntry(start, line_number);
    }

    /**
     * Indicate a local variable's use information be recorded in the
     * ClassFile as a debugging aid. If the LocalVariable doesn't provide
     * both a start and end location, then its information is not recorded.
     * This method should be called at most once per LocalVariable instance.
     */
    public void localVariableUse(LocalVariable localVar) {
        if (mLocalVariableTable == null) {
            addAttribute(new LocalVariableTableAttr(getConstantPool()));
        }
        mLocalVariableTable.addEntry(localVar);
    }

    public StackMapTableAttr getStackMapTable() {
        return mStackMapTable;
    }

    public void setInitialStackMapFrame(MethodInfo method) {
        if (mStackMapTable == null) {
            mStackMapTable = new StackMapTableAttr(getConstantPool());
        }
        mStackMapTable.setInitialFrame(method);
    }

    public void addAttribute(Attribute attr) {
        if (attr instanceof LineNumberTableAttr) {
            if (mLineNumberTable != null) {
                mAttributes.remove(mLineNumberTable);
            }
            mLineNumberTable = (LineNumberTableAttr)attr;
        } else if (attr instanceof LocalVariableTableAttr) {
            if (mLocalVariableTable != null) {
                mAttributes.remove(mLocalVariableTable);
            }
            mLocalVariableTable = (LocalVariableTableAttr)attr;
        } else if (attr instanceof StackMapTableAttr) {
            if (mStackMapTable != null) {
                mAttributes.remove(mStackMapTable);
            }
            mStackMapTable = (StackMapTableAttr)attr;
        }

        mAttributes.add(attr);
    }
    
    public Attribute[] getAttributes() {
        return mAttributes.toArray(new Attribute[mAttributes.size()]);
    }

    /**
     * Returns the length (in bytes) of this object in the class file.
     */
    public int getLength() {
        int length = 12;

        if (mCodeBuffer != null) {
            length += mCodeBuffer.getByteCodes().length;
            ExceptionHandler[] handlers = mCodeBuffer.getExceptionHandlers();
            if (handlers != null) {
                length += 8 * handlers.length;
            }
        }
        
        int size = mAttributes.size();
        for (int i=0; i<size; i++) {
            length += mAttributes.get(i).getLength();
            length += 6; // attributes have an intial 6 byte length
        }
        
        return length;
    }

    public void writeDataTo(DataOutput dout) throws IOException {
        if (mCodeBuffer == null) {
            throw new IllegalStateException("CodeAttr has no CodeBuffer set");
        }

        ExceptionHandler[] handlers = mCodeBuffer.getExceptionHandlers();
        
        dout.writeShort(mCodeBuffer.getMaxStackDepth());
        dout.writeShort(mCodeBuffer.getMaxLocals());
        
        byte[] byteCodes = mCodeBuffer.getByteCodes();
        dout.writeInt(byteCodes.length);
        dout.write(byteCodes);

        if (handlers != null) {
            int exceptionHandlerCount = handlers.length;
            dout.writeShort(exceptionHandlerCount);

            for (int i=0; i<exceptionHandlerCount; i++) {
                handlers[i].writeTo(dout);
            }
        } else {
            dout.writeShort(0);
        }
        
        int size = mAttributes.size();
        dout.writeShort(size);
        for (int i=0; i<size; i++) {
            Attribute attr = mAttributes.get(i);
            attr.writeTo(dout);
        }

        mOldLineNumberTable = null;
        mOldLocalVariableTable = null;
        mOldStackMapTable = null;
    }
} public class DeprecatedAttr extends Attribute {

    public DeprecatedAttr(ConstantPool cp) {
        super(cp, DEPRECATED);
    }

    public DeprecatedAttr(ConstantPool cp, String name) {
        super(cp, name);
    }

    public DeprecatedAttr(ConstantPool cp, String name, int length, DataInput din)
        throws IOException
    {
        super(cp, name);
        skipBytes(din, length);
    }

    public int getLength() {
        return 0;
    }
} public class EnclosingMethodAttr extends Attribute {
    private final ConstantClassInfo mClass;
    private final ConstantNameAndTypeInfo mMethod;

    public EnclosingMethodAttr(ConstantPool cp,
                               ConstantClassInfo enclosingClass,
                               ConstantNameAndTypeInfo enclosingMethod) {
        this(cp, ENCLOSING_METHOD, enclosingClass, enclosingMethod);
    }

    public EnclosingMethodAttr(ConstantPool cp, String name,
                               ConstantClassInfo enclosingClass,
                               ConstantNameAndTypeInfo enclosingMethod) {
        super(cp, name);
        mClass = enclosingClass;
        mMethod = enclosingMethod;
    }
    
    public EnclosingMethodAttr(ConstantPool cp, String name, int length, DataInput din)
        throws IOException
    {
        super(cp, name);
        mClass = (ConstantClassInfo)cp.getConstant(din.readUnsignedShort());
        mMethod = (ConstantNameAndTypeInfo)cp.getConstant(din.readUnsignedShort());
        skipBytes(din, length - 4);
    }

    public ConstantClassInfo getEnclosingClass() {
        return mClass;
    }

    public ConstantNameAndTypeInfo getEnclosingMethod() {
        return mMethod;
    }

    public int getLength() {
        return 4;
    }
    
    public void writeDataTo(DataOutput dout) throws IOException {
        dout.writeShort(mClass.getIndex());
        dout.writeShort(mMethod.getIndex());
    }
} public class ExceptionsAttr extends Attribute {

    private List<ConstantClassInfo> mExceptions = new ArrayList<ConstantClassInfo>(2);
    
    public ExceptionsAttr(ConstantPool cp) {
        super(cp, EXCEPTIONS);
    }

    public ExceptionsAttr(ConstantPool cp, String name) {
        super(cp, name);
    }
    
    public ExceptionsAttr(ConstantPool cp, String name, int length, DataInput din)
        throws IOException
    {
        super(cp, name);
        
        int size = din.readUnsignedShort();
        length -= 2;

        for (int i=0; i<size; i++) {
            int index = din.readUnsignedShort();
            length -= 2;
            ConstantClassInfo info = (ConstantClassInfo)cp.getConstant(index);
            addException(info);
        }

        skipBytes(din, length);
    }

    public ConstantClassInfo[] getExceptions() {
        return mExceptions.toArray(new ConstantClassInfo[mExceptions.size()]);
    }

    public void addException(ConstantClassInfo type) {
        mExceptions.add(type);
    }
    
    public int getLength() {
        return 2 + 2 * mExceptions.size();
    }
    
    public void writeDataTo(DataOutput dout) throws IOException {
        int size = mExceptions.size();
        dout.writeShort(size);
        for (int i=0; i<size; i++) {
            ConstantClassInfo info = mExceptions.get(i);
            dout.writeShort(info.getIndex());
        }
    }
} public class RuntimeVisibleAnnotationsAttr extends AnnotationsAttr {

    public RuntimeVisibleAnnotationsAttr(ConstantPool cp) {
        super(cp, RUNTIME_VISIBLE_ANNOTATIONS);
    }

    public RuntimeVisibleAnnotationsAttr(ConstantPool cp, String name) {
        super(cp, name);
    }
    
    public RuntimeVisibleAnnotationsAttr(ConstantPool cp, String name, int length, DataInput din)
        throws IOException
    {
        super(cp, name, length, din);
    }
} public class SignatureAttr extends Attribute {
    private final ConstantUTFInfo mSignature;
    
    public SignatureAttr(ConstantPool cp, ConstantUTFInfo signature) {
        this(cp, SIGNATURE, signature);
    }

    public SignatureAttr(ConstantPool cp, String name, ConstantUTFInfo signature) {
        super(cp, name);
        mSignature = signature;
    }
    
    public SignatureAttr(ConstantPool cp, String name, int length, DataInput din)
        throws IOException
    {
        super(cp, name);
        int index = din.readUnsignedShort();
        skipBytes(din, length - 2);
        mSignature = (ConstantUTFInfo) cp.getConstant(index);
    }

    /**
     * Returns the signature.
     */
    public ConstantUTFInfo getSignature() {
        return mSignature;
    }
    
    /**
     * Returns the length of the signature attribute, which is 2 bytes.
     */
    public int getLength() {
        return 2;
    }
    
    public void writeDataTo(DataOutput dout) throws IOException {
        dout.writeShort(mSignature.getIndex());
    }
} public class SourceFileAttr extends Attribute {

    private final ConstantUTFInfo mSourcefile;
    
    public SourceFileAttr(ConstantPool cp, String fileName) {
        this(cp, SOURCE_FILE, fileName);
    }

    public SourceFileAttr(ConstantPool cp, String name, String fileName) {
        super(cp, name);
        mSourcefile = cp.addConstantUTF(fileName);
    }
    
    public SourceFileAttr(ConstantPool cp, String name, int length, DataInput din)
        throws IOException
    {
        super(cp, name);
        int index = din.readUnsignedShort();
        skipBytes(din, length - 2);
        mSourcefile = (ConstantUTFInfo)cp.getConstant(index);
    }

    /**
     * Returns the source file name.
     */
    public ConstantUTFInfo getFileName() {
        return mSourcefile;
    }
    
    /**
     * Returns the length of the source file attribute, which is 2 bytes.
     */
    public int getLength() {
        return 2;
    }
    
    public void writeDataTo(DataOutput dout) throws IOException {
        dout.writeShort(mSourcefile.getIndex());
    }
} public class SyntheticAttr extends Attribute {

    public SyntheticAttr(ConstantPool cp) {
        super(cp, SYNTHETIC);
    }

    public SyntheticAttr(ConstantPool cp, String name) {
        super(cp, name);
    }

    public SyntheticAttr(ConstantPool cp, String name, int length, DataInput din)
        throws IOException
    {
        super(cp, name);
        skipBytes(din, length);
    }

    public int getLength() {
        return 0;
    }
} public class ConstantClassInfo extends ConstantInfo {
    private final TypeDesc mType;
    private final ConstantUTFInfo mNameConstant;
    
    public ConstantClassInfo(ConstantUTFInfo nameConstant) {
        super(TAG_CLASS);
        String name = nameConstant.getValue();
        if (!name.endsWith(";") && !name.startsWith("[")) {
            mType = TypeDesc.forClass(name);
        } else {
            mType = TypeDesc.forDescriptor(name);
        }
        mNameConstant = nameConstant;
    }

    public ConstantClassInfo(ConstantPool cp, String className) {
        super(TAG_CLASS);
        String desc = className.replace('.', '/');
        mType = TypeDesc.forClass(className);
        mNameConstant = cp.addConstantUTF(desc);
    }
    
    /**
     * Used to describe an array class.
     */
    public ConstantClassInfo(ConstantPool cp, String className, int dim) {
        super(TAG_CLASS);
        TypeDesc type = TypeDesc.forClass(className);
        String desc;
        if (dim > 0) {
            while (--dim >= 0) {
                type = type.toArrayType();
            }
            desc = type.getDescriptor();
        } else {
            desc = className.replace('.', '/');
        }
        mType = type;
        mNameConstant = cp.addConstantUTF(desc);
    }
    
    public ConstantClassInfo(ConstantPool cp, TypeDesc type) {
        super(TAG_CLASS);
        String desc;
        if (type.isArray()) {
            desc = type.getDescriptor();
        } else {
            desc = type.getRootName().replace('.', '/');
        }
        mType = type;
        mNameConstant = cp.addConstantUTF(desc);
    }

    public TypeDesc getType() {
        return mType;
    }

    public int hashCode() {
        return mType.hashCode();
    }
    
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof ConstantClassInfo) {
            ConstantClassInfo other = (ConstantClassInfo)obj;
            return mType.equals(other.mType);
        }
        return false;
    }
    
    public void writeTo(DataOutput dout) throws IOException {
        super.writeTo(dout);
        dout.writeShort(mNameConstant.getIndex());
    }

    public String toString() {
        return "CONSTANT_Class_info: ".concat(getType().getFullName());
    }
} public class ConstantUTFInfo extends ConstantInfo {
    private final String mStr;
    
    public ConstantUTFInfo(String str) {
        super(TAG_UTF8);
        mStr = str;
    }
    
    public String getValue() {
        return mStr;
    }

    public int hashCode() {
        return mStr.hashCode();
    }
    
    public boolean equals(Object obj) {
        if (obj instanceof ConstantUTFInfo) {
            ConstantUTFInfo other = (ConstantUTFInfo)obj;
            return mStr.equals(other.mStr);
        }
        
        return false;
    }
    
    public void writeTo(DataOutput dout) throws IOException {
        super.writeTo(dout);
        dout.writeUTF(mStr);
    }

    public String toString() {
        return "CONSTANT_Utf8_info: " + getValue();
    }
}
import java.util.ArrayList;
import java.util.List;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.lang.reflect.Modifier;
import org.cojen.classfile.attribute.Annotation;
import org.cojen.classfile.attribute.AnnotationsAttr;
import org.cojen.classfile.attribute.CodeAttr;
import org.cojen.classfile.attribute.DeprecatedAttr;
import org.cojen.classfile.attribute.EnclosingMethodAttr;
import org.cojen.classfile.attribute.ExceptionsAttr;
import org.cojen.classfile.attribute.RuntimeInvisibleAnnotationsAttr;
import org.cojen.classfile.attribute.RuntimeVisibleAnnotationsAttr;
import org.cojen.classfile.attribute.SignatureAttr;
import org.cojen.classfile.attribute.SourceFileAttr;
import org.cojen.classfile.attribute.SyntheticAttr;
import org.cojen.classfile.constant.ConstantClassInfo;
import org.cojen.classfile.constant.ConstantUTFInfo;
    /**
     * Returns the exceptions that this method is declared to throw.
     */
    public TypeDesc[] getExceptions() {
        if (mExceptions == null) {
            return new TypeDesc[0];
        }

        ConstantClassInfo[] classes = mExceptions.getExceptions();
        TypeDesc[] types = new TypeDesc[classes.length];
        for (int i=0; i<types.length; i++) {
            types[i] = classes[i].getType();
        }

        return types;
    }

    /**
     * Returns a CodeAttr object used to manipulate the method code body, or
     * null if this method is abstract or native.
     */
    public CodeAttr getCodeAttr() {
        return mCode;
    }

    public boolean isSynthetic() {
        for (int i = mAttributes.size(); --i >= 0; ) {
            Attribute attr = mAttributes.get(i);
            if (attr instanceof SyntheticAttr) {
                return true;
            }
        }
        return false;
    }

    public boolean isDeprecated() {
        for (int i = mAttributes.size(); --i >= 0; ) {
            Attribute attr = mAttributes.get(i);
            if (attr instanceof DeprecatedAttr) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns all the runtime invisible annotations defined for this class
     * file, or an empty array if none.
     */
    public Annotation[] getRuntimeInvisibleAnnotations() {
        for (int i = mAttributes.size(); --i >= 0; ) {
            Attribute attr = mAttributes.get(i);
            if (attr instanceof RuntimeInvisibleAnnotationsAttr) {
                return ((AnnotationsAttr) attr).getAnnotations();
            }
        }
        return new Annotation[0];
    }

    /**
     * Returns all the runtime visible annotations defined for this class file,
     * or an empty array if none.
     */
    public Annotation[] getRuntimeVisibleAnnotations() {
        for (int i = mAttributes.size(); --i >= 0; ) {
            Attribute attr = mAttributes.get(i);
            if (attr instanceof RuntimeVisibleAnnotationsAttr) {
                return ((AnnotationsAttr) attr).getAnnotations();
            }
        }
        return new Annotation[0];
    }

    /**
     * Add a runtime invisible annotation.
     */
    public Annotation addRuntimeInvisibleAnnotation(TypeDesc type) {
        AnnotationsAttr attr = null;
        for (int i = mAttributes.size(); --i >= 0; ) {
            Attribute a = mAttributes.get(i);
            if (a instanceof RuntimeInvisibleAnnotationsAttr) {
                attr = (AnnotationsAttr) a;
            }
        }
        if (attr == null) {
            attr = new RuntimeInvisibleAnnotationsAttr(mCp);
            addAttribute(attr);
        }
        Annotation ann = new Annotation(mCp);
        ann.setType(type);
        attr.addAnnotation(ann);
        return ann;
    }

    /**
     * Add a runtime visible annotation.
     */
    public Annotation addRuntimeVisibleAnnotation(TypeDesc type) {
        AnnotationsAttr attr = null;
        for (int i = mAttributes.size(); --i >= 0; ) {
            Attribute a = mAttributes.get(i);
            if (a instanceof RuntimeVisibleAnnotationsAttr) {
                attr = (AnnotationsAttr) a;
            }
        }
        if (attr == null) {
            attr = new RuntimeVisibleAnnotationsAttr(mCp);
            addAttribute(attr);
        }
        Annotation ann = new Annotation(mCp);
        ann.setType(type);
        attr.addAnnotation(ann);
        return ann;
    }

    /**
     * Returns the signature attribute of this method, or null if none is
     * defined.
     */
    // TODO: Eventually remove this method
    public SignatureAttr getSignatureAttr() {