from collections import OrderedDict

from .constants import Classref, Float, Integer, NameAndType, Utf8
from .opcodes import Opcode
from .signatures import method_descriptor


# From: http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html

##########################################################################
# 4.7. Attributes
##########################################################################

# Attributes are used in the ClassFile, field_info, method_info, and
# Code_attribute structures (§4.1, §4.5, §4.6, §4.7.3) of the class file format.


class Attribute:
    # u2 attribute_name_index;
    # u4 attribute_length;
    # u1 info[attribute_length];

    def __init__(self):
        self.name = Utf8(self.__class__.__name__)

    def __repr__(self):
        return '<%s>' % self.__class__.__name__

    @staticmethod
    def read(reader, dump=None):
        name = reader.constant_pool[reader.read_u2()].bytes.decode('mutf-8')
        size = reader.read_u4()
        if dump is not None:
            reader.debug("    " * dump, '%s (%s bytes)' % (name, size))

        try:
            return globals()[name].read_info(reader, dump + 1 if dump is not None else dump)
        except KeyError:
            # Unknown attribute - just read the bytes and ignore them.
            if dump is not None:
                reader.debug("    " * (dump + 1), 'Reading and ignoring %s bytes' % size)

            reader.read_bytes(size)

    def write(self, writer):
        writer.write_u2(writer.constant_pool.index(self.name))
        writer.write_u4(len(self))
        self.write_info(writer)

    def write_info(self, writer):
        pass

    def resolve(self, constant_pool):
        constant_pool.add(self.name)
        self.resolve_info(constant_pool)

    def resolve_info(self, constant_pool):
        pass

# For all attributes, the attribute_name_index must be a valid unsigned 16-bit
# index into the constant pool of the class. The constant_pool entry at
# attribute_name_index must be a CONSTANT_Utf8_info structure (§4.4.7)
# representing the name of the attribute. The value of the attribute_length item
# indicates the length of the subsequent information in bytes. The length does
# not include the initial six bytes that contain the attribute_name_index and
# attribute_length items.

# Certain attributes are predefined as part of the class file specification.
# They are listed in Table 4.6, accompanied by the version of the Java SE
# platform and the version of the class file format in which each first
# appeared. Within the context of their use in this specification, that is, in
# the attributes tables of the class file structures in which they appear, the
# names of these predefined attributes are reserved. Of the predefined
# attributes:

# The ConstantValue, Code and Exceptions attributes must be recognized and
# correctly read by a class file reader for correct interpretation of the class
# file by a Java Virtual Machine implementation.

# The InnerClasses, EnclosingMethod and Synthetic attributes must be recognized
# and correctly read by a class file reader in order to properly implement the
# Java SE platform class libraries (§2.12).

# The RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations,
# RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations and
# AnnotationDefault attributes must be recognized and correctly read by a class
# file reader in order to properly implement the Java SE platform class
# libraries (§2.12), if the class file's version number is 49.0 or above and the
# Java Virtual Machine implementation recognizes class files whose version
# number is 49.0 or above.

# The Signature attribute must be recognized and correctly read by a class file
# reader if the class file's version number is 49.0 or above and the Java
# Virtual Machine implementation recognizes class files whose version number is
# 49.0 or above.

# The StackMapTable attribute must be recognized and correctly read by a class
# file reader if the class file's version number is 50.0 or above and the Java
# Virtual Machine implementation recognizes class files whose version number is
# 50.0 or above.

# The BootstrapMethods attribute must be recognized and correctly read by a
# class file reader if the class file's version number is 51.0 or above and the
# Java Virtual Machine implementation recognizes class files whose version
# number is 51.0 or above.

# Use of the remaining predefined attributes is optional; a class file reader
# may use the information they contain, or otherwise must silently ignore those
# attributes.

# Table 4.6. Predefined class file attributes

# Attribute   Section Java SE class file
# ConstantValue   §4.7.2  1.0.2   45.3
# Code    §4.7.3  1.0.2   45.3
# StackMapTable   §4.7.4  6   50.0
# Exceptions  §4.7.5  1.0.2   45.3
# InnerClasses    §4.7.6  1.1 45.3
# EnclosingMethod §4.7.7  5.0 49.0
# Synthetic   §4.7.8  1.1 45.3
# Signature   §4.7.9  5.0 49.0
# SourceFile  §4.7.10 1.0.2   45.3
# SourceDebugExtension    §4.7.11 5.0 49.0
# LineNumberTable §4.7.12 1.0.2   45.3
# LocalVariableTable  §4.7.13 1.0.2   45.3
# LocalVariableTypeTable  §4.7.14 5.0 49.0
# Deprecated  §4.7.15 1.1 45.3
# RuntimeVisibleAnnotations   §4.7.16 5.0 49.0
# RuntimeInvisibleAnnotations §4.7.17 5.0 49.0
# RuntimeVisibleParameterAnnotations  §4.7.18 5.0 49.0
# RuntimeInvisibleParameterAnnotations    §4.7.19 5.0 49.0
# AnnotationDefault   §4.7.20 5.0 49.0
# BootstrapMethods    §4.7.21 7   51.0

# ------------------------------------------------------------------------
# 4.7.1. Defining and Naming New Attributes
# ------------------------------------------------------------------------

# Compilers are permitted to define and emit class files containing new
# attributes in the attributes tables of class file structures. Java Virtual
# Machine implementations are permitted to recognize and use new attributes
# found in the attributes tables of class file structures. However, any
# attribute not defined as part of this Java Virtual Machine specification must
# not affect the semantics of class or interface types. Java Virtual Machine
# implementations are required to silently ignore attributes they do not
# recognize.

# For instance, defining a new attribute to support vendor-specific debugging is
# permitted. Because Java Virtual Machine implementations are required to ignore
# attributes they do not recognize, class files intended for that particular
# Java Virtual Machine implementation will be usable by other implementations
# even if those implementations cannot make use of the additional debugging
# information that the class files contain.

# Java Virtual Machine implementations are specifically prohibited from throwing
# an exception or otherwise refusing to use class files simply because of the
# presence of some new attribute. Of course, tools operating on class files may
# not run correctly if given class files that do not contain all the attributes
# they require.

# Two attributes that are intended to be distinct, but that happen to use the
# same attribute name and are of the same length, will conflict on
# implementations that recognize either attribute. Attributes defined other than
# in this specification must have names chosen according to the package naming
# convention described in The Java Language Specification, Java SE 7 Edition
# (JLS §6.1).

# Future versions of this specification may define additional attributes.

# ------------------------------------------------------------------------
# 4.7.2. The ConstantValue Attribute
# ------------------------------------------------------------------------

# The ConstantValue attribute is a fixed-length attribute in the attributes
# table of a field_info structure (§4.5). A ConstantValue attribute represents
# the value of a constant field. There can be no more than one ConstantValue
# attribute in the attributes table of a given field_info structure. If the
# field is static (that is, the ACC_STATIC flag (Table 4.4) in the access_flags
# item of the field_info structure is set) then the constant field represented
# by the field_info structure is assigned the value referenced by its
# ConstantValue attribute as part of the initialization of the class or
# interface declaring the constant field (§5.5). This occurs prior to the
# invocation of the class or interface initialization method (§2.9) of that
# class or interface.

# If a field_info structure representing a non-static field has a ConstantValue
# attribute, then that attribute must silently be ignored. Every Java Virtual
# Machine implementation must recognize ConstantValue attributes.

# The ConstantValue attribute has the following format:

# ConstantValue_attribute {
#     u2 attribute_name_index;
#     u4 attribute_length;
#     u2 constantvalue_index;
# }
# The items of the ConstantValue_attribute structure are as follows:

# attribute_name_index
# The value of the attribute_name_index item must be a valid index into the
# constant_pool table. The constant_pool entry at that index must be a
# CONSTANT_Utf8_info (§4.4.7) structure representing the string
# "ConstantValue".

# attribute_length
# The value of the attribute_length item of a ConstantValue_attribute structure
# must be 2.

# constantvalue_index
# The value of the constantvalue_index item must be a valid index into the
# constant_pool table. The constant_pool entry at that index gives the constant
# value represented by this attribute. The constant_pool entry must be of a
# type appropriate to the field, as shown by Table 4.7.

# Table 4.7. Constant value attribute types

# Field Type  Entry Type
# long    CONSTANT_Long
# float   CONSTANT_Float
# double  CONSTANT_Double
# int, short, char, byte, boolean CONSTANT_Integer
# String  CONSTANT_String

# ------------------------------------------------------------------------
# 4.7.3. The Code Attribute
# ------------------------------------------------------------------------

# The Code attribute is a variable-length attribute in the attributes table of a
# method_info (§4.6) structure. A Code attribute contains the Java Virtual
# Machine instructions and auxiliary information for a single method, instance
# initialization method (§2.9), or class or interface initialization method
# (§2.9). Every Java Virtual Machine implementation must recognize Code
# attributes. If the method is either native or abstract, its method_info
# structure must not have a Code attribute. Otherwise, its method_info structure
# must have exactly one Code attribute.


class ExceptionInfo:
    def __init__(self, start_pc, end_pc, handler_pc, catch_type):
        # The values of the two items start_pc and end_pc indicate the ranges in
        # the code array at which the exception handler is active. The value of
        # start_pc must be a valid index into the code array of the opcode of an
        # instruction. The value of end_pc either must be a valid index into the
        # code array of the opcode of an instruction or must be equal to
        # code_length, the length of the code array. The value of start_pc must
        # be less than the value of end_pc.

        # The start_pc is inclusive and end_pc is exclusive; that is, the
        # exception handler must be active while the program counter is within
        # the interval [start_pc, end_pc).

        # The fact that end_pc is exclusive is a historical mistake in the
        # design of the Java Virtual Machine: if the Java Virtual Machine code
        # for a method is exactly 65535 bytes long and ends with an instruction
        # that is 1 byte long, then that instruction cannot be protected by an
        # exception handler. A compiler writer can work around this bug by
        # limiting the maximum size of the generated Java Virtual Machine code
        # for any method, instance initialization method, or static initializer
        # (the size of any code array) to 65534 bytes.
        self.start_pc = start_pc
        self.end_pc = end_pc

        # The value of the handler_pc item indicates the start of the exception
        # handler. The value of the item must be a valid index into the code
        # array and must be the index of the opcode of an instruction.
        self.handler_pc = handler_pc

        # If the value of the catch_type item is nonzero, it must be a valid
        # index into the constant_pool table. The constant_pool entry at that
        # index must be a CONSTANT_Class_info structure (§4.4.1) representing a
        # class of exceptions that this exception handler is designated to
        # catch. The exception handler will be called only if the thrown
        # exception is an instance of the given class or one of its subclasses.

        # If the value of the catch_type item is zero, this exception handler is
        # called for all exceptions. This is used to implement finally (§3.13).
        if catch_type is None:
            self.catch_type = None
        else:
            self.catch_type = Classref(catch_type)

    @staticmethod
    def read(reader, dump=None):
        start_pc = reader.read_u2()
        end_pc = reader.read_u2()
        handler_pc = reader.read_u2()
        item = reader.read_u2()
        if item != 0:
            catch_type = reader.constant_pool[item].name.bytes.decode('mutf-8')
        else:
            catch_type = None

        if dump is not None:
            reader.debug("    " * dump, '%s: %s-%s [%s]' % (
                catch_type if catch_type else 'finally', start_pc, end_pc, handler_pc,
            ))

        return ExceptionInfo(start_pc, end_pc, handler_pc, catch_type)

    def write(self, writer):
        writer.write_u2(self.start_pc)
        writer.write_u2(self.end_pc)
        writer.write_u2(self.handler_pc)
        if self.catch_type is None:
            writer.write_u2(0)
        else:
            writer.write_u2(writer.constant_pool.index(self.catch_type))

    def resolve(self, constant_pool):
        if self.catch_type:
            self.catch_type.resolve(constant_pool)

    def __len__(self):
        return 2 + 2 + 2 + 2


class Code(Attribute):
    # u2 attribute_name_index;
    # u4 attribute_length;
    # u2 max_stack;
    # u2 max_locals;
    # u4 code_length;
    # u1 code[code_length];
    # u2 exception_table_length;
    # {   u2 start_pc;
    #     u2 end_pc;
    #     u2 handler_pc;
    #     u2 catch_type;
    # } exception_table[exception_table_length];
    # u2 attributes_count;
    # attribute_info attributes[attributes_count];

    def __init__(self, max_stack, max_locals, code, exceptions=None, attributes=None):
        super(Code, self).__init__()
        # The value of the max_stack item gives the maximum depth of the operand
        # stack of this method (§2.6.2) at any point during execution of the
        # method.
        self.max_stack = max_stack

        # The value of the max_locals item gives the number of local variables
        # in the local variable array allocated upon invocation of this method
        # (§2.6.1), including the local variables used to pass parameters to the
        # method on its invocation.

        # The greatest local variable index for a value of type long or double
        # is max_locals - 2. The greatest local variable index for a value of
        # any other type is max_locals - 1.
        self.max_locals = max_locals

        # The code array gives the actual bytes of Java Virtual Machine code
        # that implement the method.

        # When the code array is read into memory on a byte-addressable machine,
        # if the first byte of the array is aligned on a 4-byte boundary, the
        # tableswitch and lookupswitch 32-bit offsets will be 4-byte aligned.
        # (Refer to the descriptions of those instructions for more information
        # on the consequences of code array alignment.)

        # The detailed constraints on the contents of the code array are
        # extensive and are given in a separate section (§4.9).
        self.code = code

        # Each entry in the exception_table array describes one exception
        # handler in the code array. The order of the handlers in the
        # exception_table array is significant (§2.10).
        self.exception_table = exceptions if exceptions else []

        # Each value of the attributes table must be an attribute structure
        # (§4.7). A Code attribute can have any number of optional attributes
        # associated with it.

        # The only attributes defined by this specification as appearing in the
        # attributes table of a Code attribute are the LineNumberTable
        # (§4.7.12), LocalVariableTable (§4.7.13), LocalVariableTypeTable
        # (§4.7.14), and StackMapTable (§4.7.4) attributes.

        # If a Java Virtual Machine implementation recognizes class files whose
        # version number is 50.0 or above, it must recognize and correctly read
        # StackMapTable (§4.7.4) attributes found in the attributes table of a
        # Code attribute of a class file whose version number is 50.0 or above.

        # A Java Virtual Machine implementation is required to silently ignore
        # any or all attributes in the attributes table of a Code attribute that
        # it does not recognize. Attributes not defined in this specification
        # are not allowed to affect the semantics of the class file, but only to
        # provide additional descriptive information (§4.7.1).
        self.attributes = attributes if attributes else []

    def __len__(self):
        return (
            2 +  # max_stack
            2 +  # max_locals
            4 +  # code_length
            self.code_length +  # code
            2 +  # exception_table_length
            sum(len(e) for e in self.exception_table) +  # exception table
            2 +  # attributes_count
            sum(6 + len(a) for a in self.attributes)  # attributes
        )

    def __repr__(self):
        return '<Code (%d opcodes)>' % len(self.code)

    @staticmethod
    def read_info(reader, dump=None):
        max_stack = reader.read_u2()
        max_locals = reader.read_u2()

        if dump is not None:
            reader.debug("    " * dump, 'Max stack: %s' % max_stack)
            reader.debug("    " * dump, 'Max locals: %s' % max_locals)

        code_length = reader.read_u4()
        if dump is not None:
            reader.debug("    " * dump, 'Bytecode: (%d bytes)' % code_length)

        code = []
        i = 0
        reader.depth = 0
        while i < code_length:
            reader.offset = i
            opcode = Opcode.read(reader, dump=dump + 1 if dump is not None else dump)
            code.append(opcode)
            i += len(opcode)

        exception_table_length = reader.read_u2()
        if dump is not None:
            reader.debug("    " * dump, 'Exceptions: (%d)' % exception_table_length)

        exceptions = []
        for i in range(0, exception_table_length):
            exceptions.append(ExceptionInfo.read(reader, dump=dump + 1 if dump is not None else dump))

        attributes_count = reader.read_u2()
        if dump is not None:
            reader.debug("    " * dump, 'Attributes: (%s)' % attributes_count)

        attributes = []
        for i in range(0, attributes_count):
            attributes.append(Attribute.read(reader, dump=dump + 1 if dump is not None else dump))

        return Code(max_stack, max_locals, code, exceptions=exceptions, attributes=attributes)

    def write_info(self, writer):
        writer.write_u2(self.max_stack)
        writer.write_u2(self.max_locals)

        writer.write_u4(self.code_length)
        for opcode in self.code:
            opcode.write(writer)

        writer.write_u2(self.exception_table_length)
        for exception in self.exception_table:
            exception.write(writer)

        writer.write_u2(self.attributes_count)
        for attribute in self.attributes:
            attribute.write(writer)

    def resolve_info(self, constant_pool):
        for opcode in self.code:
            opcode.resolve(constant_pool)

        for attribute in self.attributes:
            attribute.resolve(constant_pool)

        for exception in self.exception_table:
            exception.resolve(constant_pool)

    @property
    def code_length(self):
        """The value of the code_length item gives the number of bytes in the
        code array for this method. The value of code_length must be greater
        than zero; the code array must not be empty.
        """
        return sum(len(opcode) for opcode in self.code)

    @property
    def exception_table_length(self):
        """The value of the exception_table_length item gives the number of
        entries in the exception_table table.
        """
        return len(self.exception_table)

    @property
    def attributes_count(self):
        """The value of the attributes_count item indicates the number of
        attributes of the Code attribute.
        """
        return len(self.attributes)

# ------------------------------------------------------------------------
# 4.7.4. The StackMapTable Attribute
# ------------------------------------------------------------------------

# The StackMapTable attribute is a variable-length attribute in the attributes
# table of a Code (§4.7.3) attribute. This attribute is used during the process
# of verification by type checking (§4.10.1). A method's Code attribute may have
# at most one StackMapTable attribute.

# A StackMapTable attribute consists of zero or more stack map frames. Each
# stack map frame specifies (either explicitly or implicitly) a bytecode offset,
# the verification types (§4.10.1.2) for the local variables, and the
# verification types for the operand stack.

# The type checker deals with and manipulates the expected types of a method's
# local variables and operand stack. Throughout this section, a location refers
# to either a single local variable or to a single operand stack entry.

# We will use the terms stack map frame and type state interchangeably to
# describe a mapping from locations in the operand stack and local variables of
# a method to verification types. We will usually use the term stack map frame
# when such a mapping is provided in the class file, and the term type state
# when the mapping is used by the type checker.

# In a class file whose version number is greater than or equal to 50.0, if a
# method's Code attribute does not have a StackMapTable attribute, it has an
# implicit stack map attribute. This implicit stack map attribute is equivalent
# to a StackMapTable attribute with number_of_entries equal to zero.


class StackMapTable(Attribute):
    # The StackMapTable attribute has the following format:

    # u2 attribute_name_index;
    # u4 attribute_length;
    # u2 number_of_entries;
    # stack_map_frame entries[number_of_entries];

    def __init__(self, entries=None):
        super(StackMapTable, self).__init__()

        # The entries array gives the method's stack_map_frame structures.

        # Each stack_map_frame structure specifies the type state at a
        # particular bytecode offset. Each frame type specifies (explicitly or
        # implicitly) a value, offset_delta, that is used to calculate the
        # actual bytecode offset at which a frame applies. The bytecode offset
        # at which a frame applies is calculated by adding offset_delta + 1 to
        # the bytecode offset of the previous frame, unless the previous frame
        # is the initial frame of the method, in which case the bytecode offset
        # is offset_delta.

        # By using an offset delta rather than the actual bytecode offset we
        # ensure, by definition, that stack map frames are in the correctly
        # sorted order. Furthermore, by consistently using the formula
        # offset_delta + 1 for all explicit frames, we guarantee the absence of
        # duplicates.

        # We say that an instruction in the bytecode has a corresponding stack
        # map frame if the instruction starts at offset i in the code array of a
        # Code attribute, and the Code attribute has a StackMapTable attribute
        # whose entries array has a stack_map_frame structure that applies at
        # bytecode offset i.

        # The stack_map_frame structure consists of a one-byte tag followed by
        # zero or more bytes, giving more information, depending upon the tag.
        self.entries = entries if entries is not None else []

    def __repr__(self):
        return '<StackMapTable (%d entries)>' % self.number_of_entries

    def __len__(self):
        return 2 + sum(len(entry) for entry in self.entries)

    @property
    def number_of_entries(self):
        """The value of the number_of_entries item gives the number of
        # stack_map_frame entries in the entries table.
        """
        return len(self.entries)

    @staticmethod
    def read_info(reader, dump=None):
        number_of_entries = reader.read_u2()
        if dump is not None:
            reader.debug("    " * dump, 'Entries: (%d entries)' % number_of_entries)

        entries = []
        for i in range(0, number_of_entries):
            entries.append(StackMapFrame.read(reader, dump=dump + 1 if dump is not None else dump))

        return StackMapTable(entries)

    def write_info(self, writer):
        writer.write_u2(self.number_of_entries)
        for entry in self.entries:
            entry.write(writer)

    def resolve_info(self, constant_pool):
        for entry in self.entries:
            entry.resolve(constant_pool)


# A stack map frame may belong to one of several frame types:

class StackMapFrame:
    def __init__(self, frame_type):
        self.frame_type = frame_type

    @staticmethod
    def read(reader, dump=None):
        frame_type = reader.read_u1()
        if 0 <= frame_type <= 63:
            frameClass = SameFrame
        elif 64 <= frame_type <= 127:
            frameClass = SameLocals1StackItemFrame
        elif frame_type == 247:
            frameClass = SameLocals1StackItemFrameExtended
        elif 248 <= frame_type <= 250:
            frameClass = ChopFrame
        elif frame_type == 251:
            frameClass = SameFrameExtended
        elif 252 <= frame_type <= 254:
            frameClass = AppendFrame
        elif frame_type == 255:
            frameClass = FullFrame
        else:
            raise Exception("Unknown frame class %s" % frame_type)

        stack_map_frame = frameClass.read_info(reader, frame_type)

        if dump is not None:
            reader.debug("    " * dump, str(stack_map_frame))

        return stack_map_frame

    def write(self, writer):
        writer.write_u1(self.frame_type)
        self.write_info(writer)

    def write_info(self, writer):
        pass

    def resolve(self, constant_pool):
        pass


# All frame types, even full_frame, rely on the previous frame for some of
# their semantics. This raises the question of what is the very first frame?
# The initial frame is implicit, and computed from the method descriptor. (See
# the Prolog predicate methodInitialStackFrame (§4.10.1.6).)


class SameFrame(StackMapFrame):
    # The frame type same_frame is represented by tags in the range [0-63]. If
    # the frame type is same_frame, it means the frame has exactly the same
    # locals as the previous stack map frame and that the number of stack
    # items is zero. The offset_delta value for the frame is the value of the
    # tag item, frame_type.

    # u1 frame_type = SAME; /* 0-63 */

    def __init__(self, offset_delta):
        super().__init__(offset_delta)

    def __repr__(self):
        return '<SameFrame %s>' % self.frame_type

    def __len__(self):
        return 1

    @property
    def offset_delta(self):
        return self.frame_type

    @staticmethod
    def read_info(reader, frame_type):
        return SameFrame(frame_type)


class SameLocals1StackItemFrame(StackMapFrame):
    # The frame type same_locals_1_stack_item_frame is represented by tags in
    # the range [64, 127]. If the frame_type is
    # same_locals_1_stack_item_frame, it means the frame has exactly the same
    # locals as the previous stack map frame and that the number of stack
    # items is 1. The offset_delta value for the frame is the value
    # (frame_type - 64). There is a verification_type_info following the
    # frame_type for the one stack item.

    # u1 frame_type = SAME_LOCALS_1_STACK_ITEM; /* 64-127 */
    # verification_type_info stack[1];

    def __init__(self, offset_delta, stack):
        super().__init__(offset_delta + 64)
        self.stack = stack

    def __repr__(self):
        return '<SameLocals1StackItemFrame %s %s>' % (self.offset_delta, self.stack)

    def __len__(self):
        return 1 + len(self.stack)

    @property
    def offset_delta(self):
        return self.frame_type - 64

    @staticmethod
    def read_info(reader, frame_type):
        stack = VerificationTypeInfo.read(reader)
        return SameLocals1StackItemFrame(frame_type - 64, stack)

    def write_info(self, writer):
        self.stack.write(writer)

    def resolve(self, constant_pool):
        self.stack.resolve(constant_pool)


# Tags in the range [128-246] are reserved for future use.


class SameLocals1StackItemFrameExtended(StackMapFrame):
    # The frame type same_locals_1_stack_item_frame_extended is represented by
    # the tag 247. The frame type same_locals_1_stack_item_frame_extended
    # indicates that the frame has exactly the same locals as the previous
    # stack map frame and that the number of stack items is 1. The
    # offset_delta value for the frame is given explicitly. There is a
    # verification_type_info following the frame_type for the one stack item.

    # u1 frame_type = SAME_LOCALS_1_STACK_ITEM_EXTENDED; /* 247 */
    # u2 offset_delta;
    # verification_type_info stack[1];

    def __init__(self, offset_delta, stack):
        super().__init__(247)
        self.offset_delta = offset_delta
        self.stack = stack

    def __repr__(self):
        return '<SameLocals1StackItemFrameExtended %s>' % self.offset_delta

    def __len__(self):
        return 3 + len(self.stack)

    @staticmethod
    def read_info(reader, frame_type):
        offset_delta = reader.read_u2()
        stack = VerificationTypeInfo.read(reader)
        return SameLocals1StackItemFrameExtended(offset_delta, stack)

    def write_info(self, writer):
        writer.write_u2(self.offset_delta)
        self.stack.write(writer)

    def resolve(self, constant_pool):
        self.stack.resolve(constant_pool)


class ChopFrame(StackMapFrame):
    # The frame type chop_frame is represented by tags in the range [248-250].
    # If the frame_type is chop_frame, it means that the operand stack is
    # empty and the current locals are the same as the locals in the previous
    # frame, except that the k last locals are absent. The value of k is given
    # by the formula 251 - frame_type.

    # u1 frame_type = CHOP; /* 248-250 */
    # u2 offset_delta;

    def __init__(self, k, offset_delta):
        super().__init__(251-k)
        self.offset_delta = offset_delta

    def __repr__(self):
        return '<ChopFrame %s, k=%s>' % (self.offset_delta, self.k)

    def __len__(self):
        return 3

    @property
    def k(self):
        return 251 - self.frame_type

    @staticmethod
    def read_info(reader, frame_type):
        offset_delta = reader.read_u2()
        return ChopFrame(251 - frame_type, offset_delta)

    def write_info(self, writer):
        writer.write_u2(self.offset_delta)


class SameFrameExtended(StackMapFrame):
    # The frame type same_frame_extended is represented by the tag value 251.
    # If the frame type is same_frame_extended, it means the frame has exactly
    # the same locals as the previous stack map frame and that the number of
    # stack items is zero.

    # u1 frame_type = SAME_FRAME_EXTENDED; /* 251 */
    # u2 offset_delta;

    def __init__(self, offset_delta):
        super().__init__(251)
        self.offset_delta = offset_delta

    def __repr__(self):
        return '<SameFrameExtended %s>' % self.offset_delta

    def __len__(self):
        return 3

    @property
    def k(self):
        return 251 - self.frame_type

    @staticmethod
    def read_info(reader, frame_type):
        offset_delta = reader.read_u2()
        return SameFrameExtended(offset_delta)

    def write_info(self, writer):
        writer.write_u2(self.offset_delta)


class AppendFrame(StackMapFrame):
    # The frame type append_frame is represented by tags in the range
    # [252-254]. If the frame_type is append_frame, it means that the operand
    # stack is empty and the current locals are the same as the locals in the
    # previous frame, except that k additional locals are defined. The value
    # of k is given by the formula frame_type - 251.

    # u1 frame_type = APPEND; /* 252-254 */
    # u2 offset_delta;
    # verification_type_info locals[frame_type - 251];

    # The 0th entry in locals represents the type of the first additional local
    # variable. If locals[M] represents local variable N, then locals[M+1]
    # represents local variable N+1 if locals[M] is one of:
    #
    # * Top_variable_info
    # * Integer_variable_info
    # * Float_variable_info
    # * Null_variable_info
    # * UninitializedThis_variable_info
    # * Object_variable_info
    # * Uninitialized_variable_info
    #
    # Otherwise locals[M+1] represents local variable N+2.
    #
    # It is an error if, for any index i, locals[i] represents a local
    # variable whose index is greater than the maximum number of local
    # variables for the method.

    def __init__(self, k, offset_delta, locals):
        super().__init__(k + 251)
        self.offset_delta = offset_delta
        self.locals = locals

    def __repr__(self):
        return '<AppendFrame %s, k=%s, locals=%s>' % (self.offset_delta, self.k, self.locals)

    def __len__(self):
        return 3 + sum(len(local) for local in locals)

    @property
    def k(self):
        return self.frame_type - 251

    @staticmethod
    def read_info(reader, frame_type):
        offset_delta = reader.read_u2()
        locals = []
        for i in range(0, frame_type - 251):
            locals.append(VerificationTypeInfo.read(reader))
        return AppendFrame(frame_type - 251, offset_delta, locals)

    def write_info(self, writer):
        writer.write_u2(self.offset_delta)
        for local in self.locals:
            local.write(writer)

    def resolve(self, constant_pool):
        for local in self.locals:
            local.resolve(constant_pool)


class FullFrame(StackMapFrame):
    # The frame type full_frame is represented by the tag value 255.

    # u1 frame_type = FULL_FRAME; /* 255 */
    # u2 offset_delta;
    # u2 number_of_locals;
    # verification_type_info locals[number_of_locals];
    # u2 number_of_stack_items;
    # verification_type_info stack[number_of_stack_items];

    # The 0th entry in locals represents the type of local variable 0. If
    # locals[M] represents local variable N, then locals[M+1] represents local
    # variable N+1 if locals[M] is one of:
    #
    # * Top_variable_info
    # * Integer_variable_info
    # * Float_variable_info
    # * Null_variable_info
    # * UninitializedThis_variable_info
    # * Object_variable_info
    # * Uninitialized_variable_info
    #
    # Otherwise locals[M+1] represents local variable N+2.
    #
    # It is an error if, for any index i, locals[i] represents a local
    # variable whose index is greater than the maximum number of local
    # variables for the method.
    #
    # The 0th entry in stack represents the type of the bottom of the stack,
    # and subsequent entries represent types of stack elements closer to the
    # top of the operand stack. We shall refer to the bottom element of the
    # stack as stack element 0, and to subsequent elements as stack element 1,
    # 2 etc. If stack[M] represents stack element N, then stack[M+1]
    # represents stack element N+1 if stack[M] is one of:
    #
    # * Top_variable_info
    # * Integer_variable_info
    # * Float_variable_info
    # * Null_variable_info
    # * UninitializedThis_variable_info
    # * Object_variable_info
    # * Uninitialized_variable_info
    #
    # Otherwise, stack[M+1] represents stack element N+2.
    #
    # It is an error if, for any index i, stack[i] represents a stack entry
    # whose index is greater than the maximum operand stack size for the
    # method.

    def __init__(self, offset_delta, local_vars, stack):
        super().__init__(255)
        self.offset_delta = offset_delta
        self.locals = local_vars
        self.stack = stack

    def __repr__(self):
        return '<FullFrame %s, locals=%s, stack=%s>' % (self.offset_delta, self.locals, self.stack)

    def __len__(self):
        return (
            7
            + sum(len(local) for local in self.locals)
            + sum(len(frame) for frame in self.stack)
        )

    @staticmethod
    def read_info(reader, frame_type):
        offset_delta = reader.read_u2()

        n_local_vars = reader.read_u2()
        local_vars = []
        for i in range(0, n_local_vars):
            local_vars.append(VerificationTypeInfo.read(reader))

        n_frames = reader.read_u2()
        stack = []
        for i in range(0, n_frames):
            stack.append(VerificationTypeInfo.read(reader))
        return FullFrame(offset_delta, local_vars, stack)

    def write_info(self, writer):
        writer.write_u2(self.offset_delta)
        for local in self.locals:
            local.write(writer)

    def resolve(self, constant_pool):
        for local in self.locals:
            local.resolve(constant_pool)


# The verification_type_info structure consists of a one-byte tag followed by
# zero or more bytes, giving more information about the tag. Each
# verification_type_info structure specifies the verification type of one or
# two locations.


class VerificationTypeInfo:
    ITEM_Top = 0
    ITEM_Integer = 1
    ITEM_Float = 2
    ITEM_Long = 4
    ITEM_Double = 3
    ITEM_Null = 5
    ITEM_UninitializedThis = 6
    ITEM_Object = 7
    ITEM_Uninitialized = 8

    def __init__(self, tag):
        self.tag = tag

    def __len__(self):
        return 1

    @staticmethod
    def read(reader):
        tag = reader.read_u1()

        if tag == VerificationTypeInfo.ITEM_Top:
            return TopVariableInfo()
        elif tag == VerificationTypeInfo.ITEM_Integer:
            return IntegerVariableInfo()
        elif tag == VerificationTypeInfo.ITEM_Float:
            return FloatVariableInfo()
        elif tag == VerificationTypeInfo.ITEM_Long:
            return LongVariableInfo()
        elif tag == VerificationTypeInfo.ITEM_Double:
            return DoubleVariableInfo()
        elif tag == VerificationTypeInfo.ITEM_Null:
            return NullVariableInfo()
        elif tag == VerificationTypeInfo.ITEM_UninitializedThis:
            return UninitializedThisVariableInfo()
        elif tag == VerificationTypeInfo.ITEM_Object:
            return ObjectVariableInfo.read_info(reader)
        elif tag == VerificationTypeInfo.ITEM_Uninitialized:
            return UninitializedVariableInfo.read_info(reader)
        else:
            raise Exception("Unknow verification tag type %s" % tag)

    def write(self, writer):
        writer.write_u1(self.tag)
        self.write_info(writer)

    def write_info(self, writer):
        pass

    def resolve(self, constant_pool):
        pass


class TopVariableInfo(VerificationTypeInfo):
    # The Top_variable_info type indicates that the local variable has the
    # verification type top.

    # u1 tag = ITEM_Top; /* 0 */
    def __init__(self):
        super().__init__(0)

    def __repr__(self):
        return "top"


class IntegerVariableInfo(VerificationTypeInfo):
    # The Integer_variable_info type indicates that the location contains the
    # verification type int.

    # u1 tag = ITEM_Integer; /* 1 */
    def __init__(self):
        super().__init__(1)

    def __repr__(self):
        return "Integer"


class FloatVariableInfo(VerificationTypeInfo):
    # The Float_variable_info type indicates that the location contains the
    # verification type float.

    # u1 tag = ITEM_Float; /* 2 */
    def __init__(self):
        super().__init__(2)

    def __repr__(self):
        return "Float"


class LongVariableInfo(VerificationTypeInfo):
    # The Long_variable_info type indicates that the location contains the
    # verification type long.

    # u1 tag = ITEM_Long; /* 4 */

    # This structure gives the contents of two locations in the operand stack
    # or in the local variable array.
    #
    # If the location is a local variable, then:
    # * It must not be the local variable with the highest index.
    # * The next higher numbered local variable contains the verification type
    #   top.
    #
    # If the location is an operand stack entry, then:
    # * The current location must not be the topmost location of the operand
    #   stack.
    # * The next location closer to the top of the operand stack contains the
    #   verification type top.
    def __init__(self):
        super().__init__(4)

    def __repr__(self):
        return "Long"


class DoubleVariableInfo(VerificationTypeInfo):
    # The Double_variable_info type indicates that the location contains the
    # verification type double.

    # u1 tag = ITEM_Double; /* 3 */

    # This structure gives the contents of two locations in the operand stack
    # or in the local variable array.
    #
    # If the location is a local variable, then:
    #
    # * It must not be the local variable with the highest index.
    # * The next higher numbered local variable contains the verification type
    #   top.
    #
    # If the location is an operand stack entry, then:
    #
    # * The current location must not be the topmost location of the operand
    #   stack.
    #
    # * The next location closer to the top of the operand stack contains the
    #   verification type top.
    def __init__(self):
        super().__init__(3)

    def __repr__(self):
        return "Double"


class NullVariableInfo(VerificationTypeInfo):
    # The Null_variable_info type indicates that location contains the verification type null.

    # u1 tag = ITEM_Null; /* 5 */
    def __init__(self):
        super().__init__(5)

    def __repr__(self):
        return "Null"


class UninitializedThisVariableInfo(VerificationTypeInfo):
    # The UninitializedThis_variable_info type indicates that the location
    # contains the verification type uninitializedThis.

    # u1 tag = ITEM_UninitializedThis; /* 6 */
    def __init__(self):
        super().__init__(6)

    def __repr__(self):
        return "Unitialized this"


class ObjectVariableInfo(VerificationTypeInfo):
    # The Object_variable_info type indicates that the location contains an
    # instance of the class represented by the CONSTANT_Class_info (§4.4.1)
    # structure found in the constant_pool table at the index given by
    # cpool_index.

    # u1 tag = ITEM_Object; /* 7 */
    # u2 cpool_index;
    def __init__(self, class_name):
        super().__init__(VerificationTypeInfo.ITEM_Object)
        self.class_name = class_name
        self.klass = Classref(class_name)

    def __repr__(self):
        return "Object %s" % self.klass

    def __len__(self):
        return 3

    @staticmethod
    def read_info(reader):
        val = reader.read_u2()
        klass = reader.constant_pool[val].name.bytes.decode('mutf-8')
        return ObjectVariableInfo(klass)

    def write_info(self, writer):
        writer.write_u2(writer.constant_pool.index(self.klass))

    def resolve(self, constant_pool):
        self.klass.resolve(constant_pool)


class UninitializedVariableInfo(VerificationTypeInfo):
    # The Uninitialized_variable_info type indicates that the location
    # contains the verification type uninitialized(offset). The offset item
    # indicates the offset, in the code array of the Code attribute (§4.7.3)
    # that contains this StackMapTable attribute, of the new instruction
    # (§new) that created the object being stored in the location.

    # u1 tag = ITEM_Uninitialized /* 8 */
    # u2 offset;
    def __init__(self, offset):
        super().__init__(VerificationTypeInfo.ITEM_Uninitialized)
        self.offset = offset

    def __repr__(self):
        return "Unitialized"

    @staticmethod
    def read_info(reader):
        offset = reader.read_u2()
        return UninitializedVariableInfo(offset)

    def write_info(self, writer):
        writer.write_u2(self.offset)


# ------------------------------------------------------------------------
# 4.7.5. The Exceptions Attribute
# ------------------------------------------------------------------------

# The Exceptions attribute is a variable-length attribute in the attributes
# table of a method_info structure (§4.6). The Exceptions attribute indicates
# which checked exceptions a method may throw. There may be at most one
# Exceptions attribute in each method_info structure.

# The Exceptions attribute has the following format:

# Exceptions_attribute {
#     u2 attribute_name_index;
#     u4 attribute_length;
#     u2 number_of_exceptions;
#     u2 exception_index_table[number_of_exceptions];
# }
# The items of the Exceptions_attribute structure are as follows:

# attribute_name_index
# The value of the attribute_name_index item must be a valid index into the
# constant_pool table. The constant_pool entry at that index must be the
# CONSTANT_Utf8_info (§4.4.7) structure representing the string "Exceptions".

# attribute_length
# The value of the attribute_length item indicates the attribute length,
# excluding the initial six bytes.

# number_of_exceptions
# The value of the number_of_exceptions item indicates the number of entries in
# the exception_index_table.

# exception_index_table[]
# Each value in the exception_index_table array must be a valid index into the
# constant_pool table. The constant_pool entry referenced by each table item
# must be a CONSTANT_Class_info structure (§4.4.1) representing a class type
# that this method is declared to throw.

# A method should throw an exception only if at least one of the following
# three criteria is met:

# The exception is an instance of RuntimeException or one of its subclasses.

# The exception is an instance of Error or one of its subclasses.

# The exception is an instance of one of the exception classes specified in the
# exception_index_table just described, or one of their subclasses.

# These requirements are not enforced in the Java Virtual Machine; they are
# enforced only at compile-time.

# ------------------------------------------------------------------------
# 4.7.6. The InnerClasses Attribute
# ------------------------------------------------------------------------

# The InnerClasses attribute is a variable-length attribute in the attributes
# table of a ClassFile structure (§4.1). If the constant pool of a class or
# interface C contains a CONSTANT_Class_info entry which represents a class or
# interface that is not a member of a package, then C's ClassFile structure must
# have exactly one InnerClasses attribute in its attributes table.

class InnerClass:
    # Table 4.8. Nested class access and property flags

    ACC_PUBLIC = 0x0001  # Marked or implicitly public in source.
    ACC_PRIVATE = 0x0002  # Marked private in source.
    ACC_PROTECTED = 0x0004  # Marked protected in source.
    ACC_STATIC = 0x0008  # Marked or implicitly static in source.
    ACC_FINAL = 0x0010  # Marked final in source.
    ACC_INTERFACE = 0x0200  # Was an interface in source.
    ACC_ABSTRACT = 0x0400  # Marked or implicitly abstract in source.
    ACC_SYNTHETIC = 0x1000  # Declared synthetic; not present in the source code.
    ACC_ANNOTATION = 0x2000  # Declared as an annotation type.
    ACC_ENUM = 0x4000  # Declared as an enum type.

    def __init__(
            self, inner_class_name, outer_class_name, inner_name,
            public=True, private=False, protected=False, static=False,
            final=False, interface=False, abstract=False, synthetic=False,
            annotation=False, enum=False):

        self.public = public
        self.private = private
        self.protected = protected
        self.static = static
        self.final = final
        self.interface = interface
        self.abstract = abstract
        self.synthetic = synthetic
        self.annotation = annotation
        self.enum = enum
        # Each classes array entry contains the following four items:

        # The value of the inner_class_info_index item must be a valid index
        # into the constant_pool table. The constant_pool entry at that index
        # must be a CONSTANT_Class_info structure (§4.4.1) representing C. The
        # remaining items in the classes array entry give information about C.
        self.inner_class_name = inner_class_name
        self.inner_class = Classref(inner_class_name)

        # If C is not a member of a class or an interface (that is, if C is a
        # top-level class or interface (JLS §7.6) or a local class (JLS §14.3)
        # or an anonymous class (JLS §15.9.5)), the value of the
        # outer_class_info_index item must be zero.
        #
        # Otherwise, the value of the outer_class_info_index item must be a
        # valid index into the constant_pool table, and the entry at that index
        # must be a CONSTANT_Class_info (§4.4.1) structure representing the
        # class or interface of which C is a member.
        if outer_class_name is None:
            self.outer_class_name = None
            self.outer_class = None
        else:
            self.outer_class_name = outer_class_name
            self.outer_class = Classref(outer_class_name)

        # If C is anonymous (JLS §15.9.5), the value of the inner_name_index
        # item must be zero.
        #
        # Otherwise, the value of the inner_name_index item must be a valid
        # index into the constant_pool table, and the entry at that index must
        # be a CONSTANT_Utf8_info (§4.4.7) structure that represents the
        # original simple name of C, as given in the source code from which this
        # class file was compiled.
        if inner_name:
            self.inner_name = Utf8(inner_name)
        else:
            self.inner_name = None

    @property
    def inner_class_access_flags(self):
        # The value of the inner_class_access_flags item is a mask of flags used
        # to denote access permissions to and properties of class or interface C
        # as declared in the source code from which this class file was
        # compiled. It is used by a compiler to recover the original information
        # when source code is not available. The flags are shown in Table 4.8.
        #
        # All bits of the inner_class_access_flags item not assigned in Table
        # 4.8 are reserved for future use. They should be set to zero in
        # generated class files and should be ignored by Java Virtual Machine
        # implementations.
        return (
            (self.ACC_PUBLIC if self.public else 0) |
            (self.ACC_PRIVATE if self.private else 0) |
            (self.ACC_PROTECTED if self.protected else 0) |
            (self.ACC_STATIC if self.static else 0) |
            (self.ACC_FINAL if self.final else 0) |
            (self.ACC_INTERFACE if self.interface else 0) |
            (self.ACC_ABSTRACT if self.abstract else 0) |
            (self.ACC_SYNTHETIC if self.synthetic else 0) |
            (self.ACC_ANNOTATION if self.annotation else 0) |
            (self.ACC_ENUM if self.enum else 0)
        )


class InnerClasses(Attribute):
    # The InnerClasses attribute has the following format:

    # u2 attribute_name_index;
    # u4 attribute_length;
    # u2 number_of_classes;
    # {
    #     u2 inner_class_info_index;
    #     u2 outer_class_info_index;
    #     u2 inner_name_index;
    #     u2 inner_class_access_flags;
    # } classes[number_of_classes];

    def __init__(self, classes=None):
        super(InnerClasses, self).__init__()

        # The items of the InnerClasses_attribute structure are as follows:

        # Every CONSTANT_Class_info entry in the constant_pool table which
        # represents a class or interface C that is not a package member must
        # have exactly one corresponding entry in the classes array.

        # If a class has members that are classes or interfaces, its
        # constant_pool table (and hence its InnerClasses attribute) must refer
        # to each such member, even if that member is not otherwise mentioned by
        # the class. These rules imply that a nested class or interface member
        # will have InnerClasses information for each enclosing class and for
        # each immediate member.
        if classes:
            self.classes = classes
        else:
            self.classes = []

        # If a class file has a version number that is greater than or equal to
        # 51.0, and has an InnerClasses attribute in its attributes table, then
        # for all entries in the classes array of the InnerClasses attribute,
        # the value of the outer_class_info_index item must be zero if the value
        # of the inner_name_index item is zero.

        # Oracle's Java Virtual Machine implementation does not check the
        # consistency of an InnerClasses attribute against a class file
        # representing a class or interface referenced by the attribute.

    def __repr__(self):
        return '<EnclosingMethod %s.%s>' % (self.klass, self.method)

    def __len__(self):
        return 2 + (2 + 2 + 2 + 2) * self.number_of_classes

    @property
    def number_of_classes(self):
        # The value of the number_of_classes item indicates the number of
        # entries in the classes array.
        return len(self.classes)

    @staticmethod
    def read_info(reader, dump=None):
        n_classes = reader.read_u2()
        classes = []

        if dump is not None:
            reader.debug("    " * (dump + 1), 'Inner Classes: (%s)' % n_classes)

        for i in range(0, n_classes):
            c1 = reader.read_u2()
            inner_class = reader.constant_pool[c1].name.value
            if dump is not None:
                reader.debug("    " * (dump + 2), 'Inner Class: %s' % inner_class)

            c2 = reader.read_u2()
            if c2 == 0:
                outer_class = None
                if dump is not None:
                    reader.debug("    " * (dump + 3), 'Outer Class: (Anonymous)')
            else:
                outer_class = reader.constant_pool[c2].name.value
                if dump is not None:
                    reader.debug("    " * (dump + 3), 'Outer Class: %s' % outer_class)

            c3 = reader.read_u2()
            if c3 == 0:
                inner_name = None
            else:
                inner_name = reader.constant_pool[c3].value
                if dump is not None:
                    reader.debug("    " * (dump + 3), 'Inner Name: %s' % inner_name)

            flags = reader.read_u2()

            if dump is not None:
                access_description = ', '.join(f for f in [
                        flag if flags & mask else None
                        for flag, mask in [
                            ('public', InnerClass.ACC_PUBLIC),
                            ('private', InnerClass.ACC_PRIVATE),
                            ('protected', InnerClass.ACC_PROTECTED),
                            ('static', InnerClass.ACC_STATIC),
                            ('final', InnerClass.ACC_FINAL),
                            ('interface', InnerClass.ACC_INTERFACE),
                            ('abstract', InnerClass.ACC_ABSTRACT),
                            ('synthetic', InnerClass.ACC_SYNTHETIC),
                            ('annotation', InnerClass.ACC_ANNOTATION),
                            ('enum', InnerClass.ACC_ENUM),
                        ]
                    ] if f)
                reader.debug("    " * (dump + 3), 'Flags: 0x%04x%s' % (flags, ' (%s)') % (
                    access_description if access_description else ''
                ))

            classes.append(
                InnerClass(
                    inner_name,
                    outer_class,
                    inner_class,
                    public=bool(flags & InnerClass.ACC_PUBLIC),
                    private=bool(flags & InnerClass.ACC_PRIVATE),
                    protected=bool(flags & InnerClass.ACC_PROTECTED),
                    static=bool(flags & InnerClass.ACC_STATIC),
                    final=bool(flags & InnerClass.ACC_FINAL),
                    abstract=bool(flags & InnerClass.ACC_ABSTRACT),
                    synthetic=bool(flags & InnerClass.ACC_SYNTHETIC),
                    annotation=bool(flags & InnerClass.ACC_ANNOTATION),
                    enum=bool(flags & InnerClass.ACC_ENUM),
                )
            )

        return InnerClasses(classes)

    def write_info(self, writer):
        writer.write_u2(writer.constant_pool.index(self.inner_class))

        if self.outer_class:
            writer.write_u2(writer.constant_pool.index(self.outer_class))
        else:
            writer.write_u2(0)

        if self.inner_name:
            writer.write_u2(writer.constant_pool.index(self.inner_name))
        else:
            writer.write_u2(0)

    def resolve_info(self, constant_pool):
        self.inner_class.resolve(constant_pool)
        if self.outer_class:
            self.outer_class.resolve(constant_pool)
        if self.inner_name:
            self.inner_name.resolve(constant_pool)

# ------------------------------------------------------------------------
# 4.7.7. The EnclosingMethod Attribute
# ------------------------------------------------------------------------

# The EnclosingMethod attribute is an optional fixed-length attribute in the
# attributes table of a ClassFile structure (§4.1). A class must have an
# EnclosingMethod attribute if and only if it is a local class or an anonymous
# class. A class may have no more than one EnclosingMethod attribute.


class EnclosingMethod(Attribute):
    # The EnclosingMethod attribute has the following format:

    # u2 attribute_name_index;
    # u4 attribute_length;
    # u2 class_index;
    # u2 method_index;

    def __init__(self, class_name, method_name, descriptor):
        super(EnclosingMethod, self).__init__()

        # The items of the EnclosingMethod_attribute structure are as follows:

        # The value of the class_index item must be a valid index into the
        # constant_pool table. The constant_pool entry at that index must be a
        # CONSTANT_Class_info (§4.4.1) structure representing the innermost
        # class that encloses the declaration of the current class.
        self.class_name = class_name
        self.klass = Classref(class_name)

        # If the current class is not immediately enclosed by a method or
        # constructor, then the value of the method_index item must be zero.
        #
        # Otherwise, the value of the method_index item must be a valid index
        # into the constant_pool table. The constant_pool entry at that index
        # must be a CONSTANT_NameAndType_info structure (§4.4.6) representing
        # the name and type of a method in the class referenced by the
        # class_index attribute above.
        #
        # It is the responsibility of a Java compiler to ensure that the method
        # identified via the method_index is indeed the closest lexically
        # enclosing method of the class that contains this EnclosingMethod
        # attribute.
        self.method_name = method_name
        self.descriptor = method_descriptor(descriptor)

        self.method = NameAndType(method_name, descriptor)

    def __repr__(self):
        return '<EnclosingMethod %s.%s>' % (self.klass, self.method)

    def __len__(self):
        return 2 + 2

    @staticmethod
    def read_info(reader, dump=None):
        klass = reader.read_u2()
        name_and_type = reader.read_u2()

        if dump is not None:
            reader.debug("    " * dump, '%s.%s %s' % (
                reader.constant_pool[klass].name,
                reader.constant_pool[name_and_type].name,
                reader.constant_pool[name_and_type].descriptor
            ))

        return EnclosingMethod(
            reader.constant_pool[klass].name.bytes.decode('mutf-8'),
            reader.constant_pool[name_and_type].name.bytes.decode('mutf-8'),
            reader.constant_pool[name_and_type].descriptor.bytes.decode('mutf-8'),
        )

    def write_info(self, writer):
        writer.write_u2(writer.constant_pool.index(self.klass))
        writer.write_u2(writer.constant_pool.index(self.name_and_type))

    def resolve_info(self, constant_pool):
        self.klass.resolve(constant_pool)
        self.name_and_type.resolve(constant_pool)


# ------------------------------------------------------------------------
# 4.7.8. The Synthetic Attribute
# ------------------------------------------------------------------------

# The Synthetic attribute is a fixed-length attribute in the attributes table of
# a ClassFile, field_info, or method_info structure (§4.1, §4.5, §4.6). A class
# member that does not appear in the source code must be marked using a
# Synthetic attribute, or else it must have its ACC_SYNTHETIC flag set. The only
# exceptions to this requirement are compiler-generated methods which are not
# considered implementation artifacts, namely the instance initialization method
# representing a default constructor of the Java programming language (§2.9),
# the class initialization method (§2.9), and the Enum.values() and
# Enum.valueOf() methods.

# The Synthetic attribute was introduced in JDK release 1.1 to support nested
# classes and interfaces.

class Synthetic(Attribute):
    # The Synthetic attribute has the following format:

    # u2 attribute_name_index;
    # u4 attribute_length;
    # u2 signature_index;

    def __repr__(self):
        return '<Synthetic>'

    def __len__(self):
        return 0

    @staticmethod
    def read_info(reader, dump=None):
        return Synthetic()

    def write_info(self, writer):
        pass

    def resolve_info(self, constant_pool):
        pass

# ------------------------------------------------------------------------
# 4.7.9. The Signature Attribute
# ------------------------------------------------------------------------

# The Signature attribute is an optional fixed-length attribute in the
# attributes table of a ClassFile, field_info, or method_info structure (§4.1,
# §4.5, §4.6). The Signature attribute records generic signature information for
# any class, interface, constructor or member whose generic signature in the
# Java programming language would include references to type variables or
# parameterized types.

# The Signature attribute has the following format:


class Signature(Attribute):
    # u2 attribute_name_index;
    # u4 attribute_length;
    # u2 signature_index;

    def __init__(self, signature):
        super(Signature, self).__init__()

        # The value of the signature_index item must be a valid index into the
        # constant_pool table. The constant pool entry at that index must be a
        # CONSTANT_Utf8_info (§4.4.7) structure representing a class signature
        # (§4.3.4) if this Signature attribute is an attribute of a ClassFile
        # structure; a method signature if this Signature attribute is an
        # attribute of a method_info structure; or a field type signature
        # otherwise.
        self.signature = Utf8(signature)

    def __repr__(self):
        return '<SourceFile: %s>' % self.signature

    def __len__(self):
        return 2

    @staticmethod
    def read_info(reader, dump=None):
        signature = reader.constant_pool[reader.read_u2()].bytes.decode('mutf-8')

        if dump is not None:
            reader.debug("    " * dump, 'Signature: %s' % signature)

        return Signature(signature)

    def write_info(self, writer):
        writer.write_u2(writer.constant_pool.index(self.signature))

    def resolve_info(self, constant_pool):
        constant_pool.add(self.signature)

# ------------------------------------------------------------------------
# 4.7.10. The SourceFile Attribute
# ------------------------------------------------------------------------

# The SourceFile attribute is an optional fixed-length attribute in the
# attributes table of a ClassFile structure (§4.1). There can be no more than
# one SourceFile attribute in the attributes table of a given ClassFile
# structure.


class SourceFile(Attribute):
    # u2 attribute_name_index;
    # u4 attribute_length;
    # u2 sourcefile_index;

    def __init__(self, sourcefile_name):
        super(SourceFile, self).__init__()

        # The value of the sourcefile_index item must be a valid index into the
        # constant_pool table. The constant pool entry at that index must be a
        # CONSTANT_Utf8_info (§4.4.7) structure representing a string.

        # The string referenced by the sourcefile_index item will be
        # interpreted as indicating the name of the source file from which
        # this class file was compiled. It will not be interpreted as
        # indicating the name of a directory containing the file or an
        # absolute path name for the file; such platform-specific additional
        # information must be supplied by the run-time interpreter or
        # development tool at the time the file name is actually used.
        self.sourcefile_name = Utf8(sourcefile_name)

    def __repr__(self):
        return '<SourceFile: %s>' % self.sourcefile_name

    def __len__(self):
        return 2

    @staticmethod
    def read_info(reader, dump=None):
        sourcefile_name = reader.constant_pool[reader.read_u2()].bytes.decode('mutf-8')

        if dump is not None:
            reader.debug("    " * dump, 'Source file: %s' % sourcefile_name)

        return SourceFile(sourcefile_name)

    def write_info(self, writer):
        writer.write_u2(writer.constant_pool.index(self.sourcefile_name))

    def resolve_info(self, constant_pool):
        constant_pool.add(self.sourcefile_name)

# ------------------------------------------------------------------------
# 4.7.11. The SourceDebugExtension Attribute
# ------------------------------------------------------------------------

# The SourceDebugExtension attribute is an optional attribute in the attributes
# table of a ClassFile structure (§4.1). There can be no more than one
# SourceDebugExtension attribute in the attributes table of a given ClassFile
# structure.

# The SourceDebugExtension attribute has the following format:

# SourceDebugExtension_attribute {
#     u2 attribute_name_index;
#     u4 attribute_length;
#     u1 debug_extension[attribute_length];
# }
# The items of the SourceDebugExtension_attribute structure are as follows:

# attribute_name_index
# The value of the attribute_name_index item must be a valid index into the
# constant_pool table. The constant_pool entry at that index must be a
# CONSTANT_Utf8_info (§4.4.7) structure representing the string
# "SourceDebugExtension".

# attribute_length
# The value of the attribute_length item indicates the length of the attribute,
# excluding the initial six bytes.

# The value of the attribute_length item is thus the number of bytes in the
# debug_extension[] item.

# debug_extension[]
# The debug_extension array holds extended debugging information which has no
# semantic effect on the Java Virtual Machine. The information is represented
# using a modified UTF-8 string (§4.4.7) with no terminating zero byte.

# Note that the debug_extension array may denote a string longer than that
# which can be represented with an instance of class String.

# ------------------------------------------------------------------------
# 4.7.12. The LineNumberTable Attribute
# ------------------------------------------------------------------------

# The LineNumberTable attribute is an optional variable-length attribute in the
# attributes table of a Code (§4.7.3) attribute. It may be used by debuggers to
# determine which part of the Java Virtual Machine code array corresponds to a
# given line number in the original source file.
#
# If LineNumberTable attributes are present in the attributes table of a given
# Code attribute, then they may appear in any order. Furthermore, multiple
# LineNumberTable attributes may together represent a given line of a source
# file; that is, LineNumberTable attributes need not be one-to-one with source
# lines.


class LineNumberTable(Attribute):
    # u2 attribute_name_index;
    # u4 attribute_length;
    # u2 line_number_table_length;
    # {   u2 start_pc;
    #     u2 line_number;
    # } line_number_table[line_number_table_length];

    def __init__(self, line_numbers):
        super(LineNumberTable, self).__init__()

        # Each entry in the line_number_table array indicates that the line
        # number in the original source file changes at a given point in the
        # code array. Each line_number_table entry must contain the following
        # two items:
        #
        # * start_pc
        #     The value of the start_pc item must indicate the index into the code
        #     array at which the code for a new line in the original source file
        #     begins.
        #
        # * line_number
        #     The value of start_pc must be less than the value of the code_length
        #     item of the Code attribute of which this LineNumberTable is an
        #     attribute.
        #
        #     The value of the line_number item must give the corresponding line
        #     number in the original source file.
        self.line_number_table = line_numbers

    def __len__(self):
        return 2 + (2 + 2) * self.line_number_table_length

    @property
    def line_number_table_length(self):
        """The value of the line_number_table_length item indicates the number of
        entries in the line_number_table array.
        """
        return len(self.line_number_table)

    @staticmethod
    def read_info(reader, dump=None):
        line_number_table_length = reader.read_u2()

        if dump is not None:
            reader.debug("    " * dump, 'Line numbers (%s total):' % line_number_table_length)

        line_numbers = []
        for i in range(0, line_number_table_length):
            start_pc = reader.read_u2()
            line_number = reader.read_u2()
            if dump is not None:
                reader.debug("    " * (dump + 1), '%s: %s' % (start_pc, line_number))
            line_numbers.append((start_pc, line_number))

        return LineNumberTable(line_numbers)

    def write_info(self, writer):
        writer.write_u2(self.line_number_table_length)
        for start_pc, line_number in self.line_number_table:
            writer.write_u2(start_pc)
            writer.write_u2(line_number)


# ------------------------------------------------------------------------
# 4.7.13. The LocalVariableTable Attribute
# ------------------------------------------------------------------------

# The LocalVariableTable attribute is an optional variable-length attribute in
# the attributes table of a Code (§4.7.3) attribute. It may be used by debuggers
# to determine the value of a given local variable during the execution of a
# method.

# If LocalVariableTable attributes are present in the attributes table of a
# given Code attribute, then they may appear in any order. There may be no more
# than one LocalVariableTable attribute per local variable in the Code
# attribute.

# The LocalVariableTable attribute has the following format:

class LocalVariableTable(Attribute):
    # u2 attribute_name_index;
    # u4 attribute_length;
    # u2 local_variable_table_length;
    # {   u2 start_pc;
    #     u2 length;
    #     u2 name_index;
    #     u2 descriptor_index;
    #     u2 index;
    # } local_variable_table[local_variable_table_length];

    def __init__(self, local_variables):
        # Each entry in the local_variable_table array indicates a range of code
        # array offsets within which a local variable has a value. It also
        # indicates the index into the local variable array of the current frame
        # at which that local variable can be found.
        self.local_variables = local_variables

    def __len__(self):
        return 2 + sum(len(v) for v in self.local_variables)

    @property
    def local_variable_table_length(self):
        """The value of the local_variable_table_length item indicates the number of
        entries in the line_number_table array.
        """
        return len(self.line_number_table)

    @staticmethod
    def read_info(reader, dump=None):
        local_variable_table_length = reader.read_u2()

        if dump is not None:
            reader.debug("    " * dump, 'Local variables (%s total):' % local_variable_table_length)

        local_variables = []
        for i in range(0, local_variable_table_length):
            local_var = LocalVariable.read(reader)
            if dump is not None:
                reader.debug("    " * (dump + 1), '%s' % local_var)
            local_variables.append(local_var)

        return LineNumberTable(local_variables)

    def write_info(self, writer):
        writer.write_u2(self.local_variable_table_length)
        for local_variable in self.local_variables:
            local_variable.write(writer)


class LocalVariable:
    def __init__(self, start_pc, length, name, descriptor, index):
        # Each entry in the local_variable_table array indicates a range of code
        # array offsets within which a local variable has a value. It also
        # indicates the index into the local variable array of the current frame
        # at which that local variable can be found. Each entry must contain the
        # following five items:
        #
        # The given local variable must have a value at indices into the code
        # array in the interval [start_pc, start_pc + length), that is, between
        # start_pc inclusive and start_pc + length exclusive.

        # The value of start_pc must be a valid index into the code array of
        # this Code attribute and must be the index of the opcode of an
        # instruction.

        # The value of start_pc + length must either be a valid index into the
        # code array of this Code attribute and be the index of the opcode of an
        # instruction, or it must be the first index beyond the end of that code
        # array.
        self.start_pc = start_pc
        self.length = length

        # The value of the name_index item must be a valid index into the
        # constant_pool table. The constant_pool entry at that index must
        # contain a CONSTANT_Utf8_info (§4.4.7) structure representing a valid
        # unqualified name (§4.2.2) denoting a local variable.
        self.name = Utf8(name)

        # The value of the descriptor_index item must be a valid index into the
        # constant_pool table. The constant_pool entry at that index must
        # contain a CONSTANT_Utf8_info structure (§4.4.7) representing a field
        # descriptor (§4.3.2) encoding the type of a local variable in the
        # source program.
        self.descriptor = descriptor

        # The given local variable must be at index in the local variable
        # array of the current frame.
        #
        # If the local variable at index is of type double or long, it
        # occupies both index and index + 1.
        self.index = index

    def __len__(self):
        return 2 + 2 + 2 + 2 + 2

    def __str__(self):
        return "[%s:%s) %s: %s %s" % (
            self.start_pc,
            self.start_pc + self.length,
            self.index,
            self.descriptor,
            self.name
        )

    @staticmethod
    def read(reader, dump=None):
        start_pc = reader.read_u2()
        length = reader.read_u2()
        name = reader.constant_pool[reader.read_u2()].bytes.decode('utf-8')
        descriptor = reader.constant_pool[reader.read_u2()].bytes.decode('utf-8')
        index = reader.read_u2()

        return LocalVariable(start_pc, length, name, descriptor, index)

    def write(self, writer):
        writer.write_u2(self.start_pc)
        writer.write_u2(self.length)
        writer.write_u2(writer.constant_pool[self.name])
        writer.write_u2(writer.constant_pool[self.descriptor])
        writer.write_u2(self.index)

    def resolve(self, constant_pool):
        constant_pool.add(self.name)
        constant_pool.add(self.descriptor)

# ------------------------------------------------------------------------
# 4.7.14. The LocalVariableTypeTable Attribute
# ------------------------------------------------------------------------

# The LocalVariableTypeTable attribute is an optional variable-length attribute
# in the attributes table of a Code (§4.7.3) attribute. It may be used by
# debuggers to determine the value of a given local variable during the
# execution of a method.

# If LocalVariableTypeTable attributes are present in the attributes table of a
# given Code attribute, then they may appear in any order. There may be no more
# than one LocalVariableTypeTable attribute per local variable in the Code
# attribute.

# The LocalVariableTypeTable attribute differs from the LocalVariableTable
# attribute in that it provides signature information rather than descriptor
# information. This difference is only significant for variables whose type is
# a generic reference type. Such variables will appear in both tables, while
# variables of other types will appear only in LocalVariableTable.

# The LocalVariableTypeTable attribute has the following format:

# LocalVariableTypeTable_attribute {
#     u2 attribute_name_index;
#     u4 attribute_length;
#     u2 local_variable_type_table_length;
#     {   u2 start_pc;
#         u2 length;
#         u2 name_index;
#         u2 signature_index;
#         u2 index;
#     } local_variable_type_table[local_variable_type_table_length];
# }
# The items of the LocalVariableTypeTable_attribute structure are as follows:

# attribute_name_index
# The value of the attribute_name_index item must be a valid index into the
# constant_pool table. The constant_pool entry at that index must be a
# CONSTANT_Utf8_info (§4.4.7) structure representing the string
# "LocalVariableTypeTable".

# attribute_length
# The value of the attribute_length item indicates the length of the attribute, excluding the initial six bytes.

# local_variable_type_table_length
# The value of the local_variable_type_table_length item indicates the number
# of entries in the local_variable_type_table array.

# local_variable_type_table[]
# Each entry in the local_variable_type_table array indicates a range of code
# array offsets within which a local variable has a value. It also indicates
# the index into the local variable array of the current frame at which that
# local variable can be found. Each entry must contain the following five
# items:

# start_pc, length
# The given local variable must have a value at indices into the code array in
# the interval [start_pc, start_pc + length), that is, between start_pc
# inclusive and start_pc + length exclusive.

# The value of start_pc must be a valid index into the code array of this Code
# attribute and must be the index of the opcode of an instruction.

# The value of start_pc + length must either be a valid index into the code
# array of this Code attribute and be the index of the opcode of an
# instruction, or it must be the first index beyond the end of that code array.

# name_index
# The value of the name_index item must be a valid index into the constant_pool
# table. The constant_pool entry at that index must contain a
# CONSTANT_Utf8_info (§4.4.7) structure representing a valid unqualified name
# (§4.2.2) denoting a local variable.

# signature_index
# The value of the signature_index item must be a valid index into the
# constant_pool table. The constant_pool entry at that index must contain a
# CONSTANT_Utf8_info structure (§4.4.7) representing a field type signature
# (§4.3.4) encoding the type of a local variable in the source program.

# index
# The given local variable must be at index in the local variable array of the
# current frame.

# If the local variable at index is of type double or long, it occupies both
# index and index + 1.

# ------------------------------------------------------------------------
# 4.7.15. The Deprecated Attribute
# ------------------------------------------------------------------------

# The Deprecated attribute is an optional fixed-length attribute in the
# attributes table of a ClassFile, field_info, or method_info structure (§4.1,
# §4.5, §4.6). A class, interface, method, or field may be marked using a
# Deprecated attribute to indicate that the class, interface, method, or field
# has been superseded.

# A run-time interpreter or tool that reads the class file format, such as a
# compiler, can use this marking to advise the user that a superceded class,
# interface, method, or field is being referred to. The presence of a
# Deprecated attribute does not alter the semantics of a class or interface.
# The Deprecated attribute has the following format:

# Deprecated_attribute {
#     u2 attribute_name_index;
#     u4 attribute_length;
# }
# The items of the Deprecated_attribute structure are as follows:

# attribute_name_index
# The value of the attribute_name_index item must be a valid index into the
# constant_pool table. The constant_pool entry at that index must be a
# CONSTANT_Utf8_info (§4.4.7) structure representing the string "Deprecated".

# attribute_length
# The value of the attribute_length item is zero.

# ------------------------------------------------------------------------
# 4.7.16. The RuntimeVisibleAnnotations attribute
# ------------------------------------------------------------------------

# The RuntimeVisibleAnnotations attribute is a variable-length attribute in the
# attributes table of a ClassFile, field_info, or method_info structure (§4.1,
# §4.5, §4.6). The RuntimeVisibleAnnotations attribute records run-time-visible
# Java programming language annotations on the corresponding class, field, or
# method.

# Each ClassFile, field_info, and method_info structure may contain at most one
# RuntimeVisibleAnnotations attribute, which records all the run-time-visible
# Java programming language annotations on the corresponding program element.
# The Java Virtual Machine must make these annotations available so they can be
# returned by the appropriate reflective APIs.

# The RuntimeVisibleAnnotations attribute has the following format:


class RuntimeVisibleAnnotations(Attribute):
    # u2 attribute_name_index;
    # u4 attribute_length;
    # u2 num_annotations;
    # annotation annotations[num_annotations];

    def __init__(self, annotations):
        super(RuntimeVisibleAnnotations, self).__init__()

        # Each value of the annotations table represents a single run-time-
        # visible annotation on a program element.

        self.annotations = annotations

    def __len__(self):
        return 2 + sum(len(annotation) for annotation in self.annotations)

    @property
    def num_annotations(self):
        """The value of the num_annotations item gives the number of run-time-
        visible annotations represented by the structure.

        Note that a maximum of 65535 run-time-visible Java programming
        language annotations may be directly attached to a program element.
        """
        return len(self.annotations)

    @staticmethod
    def read_info(reader, dump=None):
        num_annotations = reader.read_u2()

        if dump is not None:
            reader.debug("    " * dump, 'RuntimeVisibleAnnotations (%s total):' % num_annotations)

        annotations = []
        for i in range(0, num_annotations):
            annotations.append(Annotation.read(reader, dump=dump + 1 if dump is not None else dump))

        return RuntimeVisibleAnnotations(annotations)

    def write_info(self, writer):
        writer.write_u2(self.num_annotations)
        for annotation in self.annotations:
            annotation.write(writer)

    def resolve_info(self, constant_pool):
        for annotation in self.annotations:
            annotation.resolve(constant_pool)


# The annotation structure has the following format:

class Annotation:
    # u2 type_index;
    # u2 num_element_value_pairs;
    # {
    #       u2 element_name_index;
    #       element_value value;
    # } element_value_pairs[num_element_value_pairs];

    def __init__(self, type_name, element_value_pairs):

        # The value of the type_index item must be a valid index into the
        # constant_pool table. The constant_pool entry at that index must be a
        # CONSTANT_Utf8_info (§4.4.7) structure representing a field descriptor
        # representing the annotation type corresponding to the annotation
        # represented by this annotation structure.

        self.type_name = Utf8(type_name)

        # element_value_pairs

        # Each value of the element_value_pairs table represents a single
        # element-value pair in the annotation represented by this annotation
        # structure. Each element_value_pairs entry contains the following two
        # items:
        #
        #   element_name_index
        #
        #       The value of the element_name_index item must be a valid index
        #       into the constant_pool table. The constant_pool entry at that
        #       index must be a CONSTANT_Utf8_info structure (§4.4.7)
        #       representing a valid field descriptor (§4.3.2) that denotes the
        #       name of the annotation type element represented by this
        #       element_value_pairs entry.
        #
        #   value
        #
        #       The value of the value item represents the value of the
        #       element-value pair represented by this element_value_pairs
        #       entry.

        self.element_value_pairs = element_value_pairs

    def __len__(self):
        return 2 + 2 + sum(
            (2 + len(element_value))
            for element_name, element_value in self.element_value_pairs.items()
        )

    @property
    def num_element_value_pairs(self):
        """The value of the num_element_value_pairs item gives the number of
        element-value pairs of the annotation represented by this annotation
        structure.

        Note that a maximum of 65535 element-value pairs may be contained in a
        single annotation.

        """
        return len(self.element_value_pairs)

    @staticmethod
    def read(reader, dump=None):
        type_name = reader.constant_pool[reader.read_u2()].bytes.decode('mutf-8')

        num_element_value_pairs = reader.read_u2()

        if dump is not None:
            reader.debug("    " * dump, '%s (%s pairs):' % (type_name, num_element_value_pairs))

        element_value_pairs = OrderedDict()
        for i in range(0, num_element_value_pairs):
            element_name = reader.constant_pool[reader.read_u2()].bytes.decode('mutf-8')
            value = ElementValue.read(reader)
            if dump is not None:
                reader.debug("    " * (dump + 1), '%s: %s' % (element_name, value))

            element_value_pairs[element_name] = value

        return Annotation(type_name, element_value_pairs)

    def write(self, writer):
        writer.write_u2(writer.constant_pool.index(self.type_name))
        writer.write_u2(self.num_element_value_pairs)
        for element_name, value in self.element_value_pairs.items():
            writer.write_u2(writer.constant_pool.index(Utf8(element_name)))
            value.write(writer)

    def resolve(self, constant_pool):
        constant_pool.add(self.type_name)
        for element_name, value in self.element_value_pairs.items():
            constant_pool.add(Utf8(element_name))
            value.resolve(constant_pool)


# ------------------------------------------------------------------------
# 4.7.16.1. The element_value structure
# ------------------------------------------------------------------------

# The element_value structure is a discriminated union representing the value of
# an element-value pair. It is used to represent element values in all
# attributes that describe annotations (RuntimeVisibleAnnotations,
# RuntimeInvisibleAnnotations, RuntimeVisibleParameterAnnotations, and
# RuntimeInvisibleParameterAnnotations).

# The element_value structure has the following format:

class ElementValue:
    # u1 tag;
    # union {
    #     u2 const_value_index;

    #     {   u2 type_name_index;
    #         u2 const_name_index;
    #     } enum_const_value;

    #     u2 class_info_index;

    #     annotation annotation_value;

    #     {   u2            num_values;
    #         element_value values[num_values];
    #     } array_value;
    # } value;

    @staticmethod
    def read(reader):
        # The tag item indicates the type of this annotation element-value pair.

        # The letters B, C, D, F, I, J, S, and Z indicate a primitive type.
        # These letters are interpreted as if they were field descriptors
        # (§4.3.2).

        # The other legal values for tag are listed with their interpretations
        # in Table 4.9.

        # Table 4.9. Interpretation of additional tag values

        # tag Value   Element Type
        #   s           String
        #   e           enum constant
        #   c           class
        #   @           annotation type
        #   [           array

        tag = chr(reader.read_u1())

        # The value item represents the value of this annotation element. This
        # item is a union. The tag item, above, determines which item of the
        # union is to be used:

        if tag in ('B', 'C', 'D', 'F', 'I', 'J', 'S', 'Z', 's'):
            value = ConstantElementValue.read(reader)
        elif tag == 'e':  # enum constant
            value = EnumConstantElementValue.read(reader)
        elif tag == 'c':  # class
            value = ClassElementValue.read(reader)
        elif tag == '@':  # annotation
            value = AnnotationElementValue.read(reader)
        elif tag == '[':  # array
            # value = ArrayElementValue.read(reader)
            raise NotImplementedError("ElementValue tag '[' not yet implemented")
        else:
            raise Exception("Unknown tag '%s'" % tag)

        return value


class ConstantElementValue:

    # The const_value_index item is used if the tag item is one of B, C, D, F, I, J, S, Z, or s.

    def __init__(self, value):
        # The value of the const_value_index item must be a valid index into the
        # constant_pool table. The constant_pool entry at that index must be of
        # the correct entry type for the field type designated by the tag item,
        # as specified in Table 4.9.
        # if isinstance(value, str):
        #     self.tag = 'B'
        #     self.value = Utf8(value)
        # elif isinstance(value, str) and len(value) == 1:
        #     self.tag = 'C'
        #     self.value = Utf8(value)
        if isinstance(value, float):
            self.tag = 'D'
            self.value = Float(value)
        # elif isinstance(value, str):
        #     self.tag = 'F'
        #     self.value = Utf8(value)
        elif isinstance(value, int):
            self.tag = 'I'
            self.value = Integer(value)
        # elif isinstance(value, str):
        #     self.tag = 'J'
        #     self.value = Utf8(value)
        # elif isinstance(value, str):
        #     self.tag = 'S'
        #     self.value = Utf8(value)
        # elif isinstance(value, bool):
        #     self.tag = 'Z'
        #     self.value = Boolean(value)
        elif isinstance(value, str):
            self.tag = 's'
            self.value = Utf8(value)
        else:

            raise Exception("Unknown constant type", type(value))

    def __len__(self):
        return 1 + 2

    def __str__(self):
        return str(self.value)

    @staticmethod
    def read(reader):
        constant = reader.constant_pool[reader.read_u2()]
        return ConstantElementValue(constant.value)

    def write(self, writer):
        writer.write_u1(ord(self.tag))
        writer.write_u2(writer.constant_pool.index(self.value))

    def resolve(self, constant_pool):
        self.value.resolve(constant_pool)


class EnumConstantElementValue:

    # The enum_const_value item is used if the tag item is e.

    def __init__(self, type_name, const_name):

        # The value of the type_name_index item must be a valid index into the
        # constant_pool table. The constant_pool entry at that index must be a
        # CONSTANT_Utf8_info structure (§4.4.7) representing a valid field
        # descriptor (§4.3.2) that denotes the internal form of the binary name
        # (§4.2.1) of the type of the enum constant represented by this
        # element_value structure.
        self.type_name = Utf8(type_name)

        # The value of the const_name_index item must be a valid index into the
        # constant_pool table. The constant_pool entry at that index must be a
        # CONSTANT_Utf8_info structure (§4.4.7) representing the simple name of
        # the enum constant represented by this element_value structure.
        self.const_name = Utf8(const_name)

    def __len__(self):
        return 1 + 2 + 2

    def __str__(self):
        return "%s = %s" % (self.type_name, self.const_name)

    @staticmethod
    def read(reader):
        type_name = reader.constant_pool[reader.read_u2()].bytes.decode('utf-8')
        const_name = reader.constant_pool[reader.read_u2()].bytes.decode('utf-8')
        return EnumConstantElementValue(type_name, const_name)

    def write(self, writer):
        writer.write_u1('e')
        writer.write_u2(writer.constant_pool.index(self.type_name))
        writer.write_u2(writer.constant_pool.index(self.const_name))

    def resolve(self, constant_pool):
        constant_pool.add(self.type_name)
        constant_pool.add(self.const_name)


class ClassElementValue:

    # The class_info_index item is used if the tag item is c.

    def __init__(self, class_name):
        # The class_info_index item must be a valid index into the constant_pool
        # table. The constant_pool entry at that index must be a
        # CONSTANT_Utf8_info (§4.4.7) structure representing the return
        # descriptor (§4.3.3) of the type that is reified by the class
        # represented by this element_value structure.

        # For example, V for Void.class, Ljava/lang/Object; for Object, etc.

        self.klass = Classref(class_name)

    def __len__(self):
        return 1 + 2

    def __str__(self):
        return str(self.value)

    @staticmethod
    def read(reader, dump=None):
        class_name = reader.constant_pool[reader.read_u2()].bytes.decode('utf-8')
        return ClassElementValue(class_name)

    def write(self, writer):
        writer.write_u1('c')
        writer.write_u2(writer.constant_pool.index(self.klass))

    def resolve(self, constant_pool):
        constant_pool.add(self.klass)


class AnnotationElementValue:

    # The annotation_value item is used if the tag item is @.

    def __init__(self, annotation):
        # The element_value structure represents a "nested" annotation.

        self.annotation = annotation

    def __len__(self):
        return 1 + len(self.annotation)

    def __str__(self):
        return str(self.annotation)

    @staticmethod
    def read(reader, dump=None):
        annotation = Annotation.read(reader, dump=dump + 1 if dump is not None else dump)
        return AnnotationElementValue(annotation)

    def write(self, writer):
        writer.write_u1('@')
        self.annotation.write(writer)

    def resolve(self, constant_pool):
        self.annotation.resolve(constant_pool)


class ArrayElementValue:

    # The array_value item is used if the tag item is [.

    def __init__(self, values):
        # Each value of the values table gives the value of an element of the
        # array-typed value represented by this element_value structure.
        self.values = values

    def __len__(self):
        return 1 + 2 + sum(len(v) for v in self.values)

    @property
    def num_values(self):
        """The value of the num_values item gives the number of elements in the
        array-typed value represented by this element_value structure.

        Note that a maximum of 65535 elements are permitted in an array-typed
        element value.
        """
        return len(self.values)

    @staticmethod
    def read(reader, dump=None):
        num_values = reader.read_u2()

        values = []
        for i in range(0, num_values):
            value = ElementValue.read(reader, dump=dump + 1 if dump is not None else dump)
            values.append(value)

        return ArrayElementValue(values)

    def write(self, writer):
        writer.write_u1('[')
        writer.write_u2(self.num_values)
        for value in self.values:
            value.write(writer)

    def resolve(self, constant_pool):
        for value in self.values:
            value.resolve(constant_pool)


# ------------------------------------------------------------------------
# 4.7.17. The RuntimeInvisibleAnnotations attribute
# ------------------------------------------------------------------------

# The RuntimeInvisibleAnnotations attribute is similar to the
# RuntimeVisibleAnnotations attribute, except that the annotations represented
# by a RuntimeInvisibleAnnotations attribute must not be made available for
# return by reflective APIs, unless the Java Virtual Machine has been instructed
# to retain these annotations via some implementation-specific mechanism such as
# a command line flag. In the absence of such instructions, the Java Virtual
# Machine ignores this attribute.

# The RuntimeInvisibleAnnotations attribute is a variable-length attribute in
# the attributes table of a ClassFile, field_info, or method_info structure
# (§4.1, §4.5, §4.6). The RuntimeInvisibleAnnotations attribute records run-
# time-invisible Java programming language annotations on the corresponding
# class, method, or field.

# Each ClassFile, field_info, and method_info structure may contain at most one
# RuntimeInvisibleAnnotations attribute, which records all the run-time-
# invisible Java programming language annotations on the corresponding program
# element.

# The RuntimeInvisibleAnnotations attribute has the following format:

class RuntimeInvisibleAnnotations(Attribute):
    # u2 attribute_name_index;
    # u4 attribute_length;
    # u2 num_annotations;
    # annotation annotations[num_annotations];

    def __init__(self, annotations):
        super(RuntimeInvisibleAnnotations, self).__init__()

        # Each value of the annotations table represents a single run-time-
        # visible annotation on a program element.

        self.annotations = annotations

    def __len__(self):
        return 2 + sum(len(annotation) for annotation in self.annotations)

    @property
    def num_annotations(self):
        """The value of the num_annotations item gives the number of run-time-
        visible annotations represented by the structure.

        Note that a maximum of 65535 run-time-visible Java programming
        language annotations may be directly attached to a program element.
        """
        return len(self.annotations)

    @staticmethod
    def read_info(reader, dump=None):
        num_annotations = reader.read_u2()

        if dump is not None:
            reader.debug("    " * dump, 'RuntimeInvisibleAnnotations (%s total):' % num_annotations)

        annotations = []
        for i in range(0, num_annotations):
            annotations.append(Annotation.read(reader, dump=dump + 1 if dump is not None else dump))

        return RuntimeInvisibleAnnotations(annotations)

    def write_info(self, writer):
        writer.write_u2(self.num_annotations)
        for annotation in self.annotations:
            annotation.write(writer)

    def resolve_info(self, constant_pool):
        for annotation in self.annotations:
            annotation.resolve(constant_pool)


# ------------------------------------------------------------------------
# 4.7.18. The RuntimeVisibleParameterAnnotations attribute
# ------------------------------------------------------------------------

# The RuntimeVisibleParameterAnnotations attribute is a variable-length
# attribute in the attributes table of the method_info structure (§4.6). The
# RuntimeVisibleParameterAnnotations attribute records run-time-visible Java
# programming language annotations on the parameters of the corresponding
# method.

# Each method_info structure may contain at most one
# RuntimeVisibleParameterAnnotations attribute, which records all the
# run-time-visible Java programming language annotations on the parameters of
# the corresponding method. The Java Virtual Machine must make these
# annotations available so they can be returned by the appropriate reflective
# APIs.

# The RuntimeVisibleParameterAnnotations attribute has the following format:

# RuntimeVisibleParameterAnnotations_attribute {
#     u2 attribute_name_index;
#     u4 attribute_length;
#     u1 num_parameters;
#     {   u2         num_annotations;
#         annotation annotations[num_annotations];
#     } parameter_annotations[num_parameters];
# }
# The items of the RuntimeVisibleParameterAnnotations_attribute structure are
# as follows:

# attribute_name_index
# The value of the attribute_name_index item must be a valid index into the
# constant_pool table. The constant_pool entry at that index must be a
# CONSTANT_Utf8_info structure (§4.4.7) representing the string
# "RuntimeVisibleParameterAnnotations".

# attribute_length
# The value of the attribute_length item indicates the length of the attribute,
# excluding the initial six bytes.

# The value of the attribute_length item is thus dependent on the number of
# parameters, the number of run-time-visible annotations on each parameter, and
# their values.

# num_parameters
# The value of the num_parameters item gives the number of parameters of the
# method represented by the method_info structure on which the annotation
# occurs. (This duplicates information that could be extracted from the method
# descriptor (§4.3.3).)

# parameter_annotations
# Each value of the parameter_annotations table represents all of the
# run-time-visible annotations on a single parameter. The sequence of values in
# the table corresponds to the sequence of parameters in the method descriptor.
# Each parameter_annotations entry contains the following two items:

# num_annotations
# The value of the num_annotations item indicates the number of
# run-time-visible annotations on the parameter corresponding to the sequence
# number of this parameter_annotations element.

# annotations
# Each value of the annotations table represents a single run-time-visible
# annotation on the parameter corresponding to the sequence number of this
# parameter_annotations element.

# ------------------------------------------------------------------------
# 4.7.19. The RuntimeInvisibleParameterAnnotations attribute
# ------------------------------------------------------------------------

# The RuntimeInvisibleParameterAnnotations attribute is similar to the
# RuntimeVisibleParameterAnnotations attribute, except that the annotations
# represented by a RuntimeInvisibleParameterAnnotations attribute must not be
# made available for return by reflective APIs, unless the Java Virtual Machine
# has specifically been instructed to retain these annotations via some
# implementation-specific mechanism such as a command line flag. In the absence
# of such instructions, the Java Virtual Machine ignores this attribute.

# The RuntimeInvisibleParameterAnnotations attribute is a variable-length
# attribute in the attributes table of a method_info structure (§4.6). The
# RuntimeInvisibleParameterAnnotations attribute records run-time-invisible
# Java programming language annotations on the parameters of the corresponding
# method.

# Each method_info structure may contain at most one
# RuntimeInvisibleParameterAnnotations attribute, which records all the
# run-time-invisible Java programming language annotations on the parameters of
# the corresponding method.

# The RuntimeInvisibleParameterAnnotations attribute has the following format:

# RuntimeInvisibleParameterAnnotations_attribute {
#     u2 attribute_name_index;
#     u4 attribute_length;
#     u1 num_parameters;
#     {   u2         num_annotations;
#         annotation annotations[num_annotations];
#     } parameter_annotations[num_parameters];
# }
# The items of the RuntimeInvisibleParameterAnnotations_attribute structure are
# as follows:

# attribute_name_index
# The value of the attribute_name_index item must be a valid index into the
# constant_pool table. The constant_pool entry at that index must be a
# CONSTANT_Utf8_info structure (§4.4.7) representing the string
# "RuntimeInvisibleParameterAnnotations".

# attribute_length
# The value of the attribute_length item indicates the length of the attribute,
# excluding the initial six bytes.

# The value of the attribute_length item is thus dependent on the number of
# parameters, the number of run-time-invisible annotations on each parameter,
# and their values.

# num_parameters
# The value of the num_parameters item gives the number of parameters of the
# method represented by the method_info structure on which the annotation
# occurs. (This duplicates information that could be extracted from the method
# descriptor (§4.3.3).)

# parameter_annotations
# Each value of the parameter_annotations table represents all of the
# run-time-invisible annotations on a single parameter. The sequence of values
# in the table corresponds to the sequence of parameters in the method
# descriptor. Each parameter_annotations entry contains the following two
# items:

# num_annotations
# The value of the num_annotations item indicates the number of
# run-time-invisible annotations on the parameter corresponding to the sequence
# number of this parameter_annotations element.

# annotations
# Each value of the annotations table represents a single run-time-invisible
# annotation on the parameter corresponding to the sequence number of this
# parameter_annotations element.

# ------------------------------------------------------------------------
# 4.7.20. The AnnotationDefault attribute
# ------------------------------------------------------------------------

# The AnnotationDefault attribute is a variable-length attribute in the
# attributes table of certain method_info structures (§4.6), namely those
# representing elements of annotation types. The AnnotationDefault attribute
# records the default value for the element represented by the method_info
# structure.

# Each method_info structure representing an element of an annotation type may
# contain at most one AnnotationDefault attribute. The Java Virtual Machine
# must make this default value available so it can be applied by appropriate
# reflective APIs.

# The AnnotationDefault attribute has the following format:

# AnnotationDefault_attribute {
#     u2            attribute_name_index;
#     u4            attribute_length;
#     element_value default_value;
# }
# The items of the AnnotationDefault_attribute structure are as follows:

# attribute_name_index
# The value of the attribute_name_index item must be a valid index into the
# constant_pool table. The constant_pool entry at that index must be a
# CONSTANT_Utf8_info structure (§4.4.7) representing the string
# "AnnotationDefault".

# attribute_length
# The value of the attribute_length item indicates the length of the attribute,
# excluding the initial six bytes.

# The value of the attribute_length item is thus dependent on the default value.

# default_value
# The default_value item represents the default value of the annotation type
# element whose default value is represented by this AnnotationDefault
# attribute.

# ------------------------------------------------------------------------
# 4.7.21. The BootstrapMethods attribute
# ------------------------------------------------------------------------

# The BootstrapMethods attribute is a variable-length attribute in the
# attributes table of a ClassFile structure (§4.1). The BootstrapMethods
# attribute records bootstrap method specifiers referenced by invokedynamic
# instructions (§invokedynamic).

# There must be exactly one BootstrapMethods attribute in the attributes table
# of a given ClassFile structure if the constant_pool table of the ClassFile
# structure has at least one CONSTANT_InvokeDynamic_info entry (§4.4.10). There
# can be no more than one BootstrapMethods attribute in the attributes table of
# a given ClassFile structure.

# The BootstrapMethods attribute has the following format:

# BootstrapMethods_attribute {
#     u2 attribute_name_index;
#     u4 attribute_length;
#     u2 num_bootstrap_methods;
#     {   u2 bootstrap_method_ref;
#         u2 num_bootstrap_arguments;
#         u2 bootstrap_arguments[num_bootstrap_arguments];
#     } bootstrap_methods[num_bootstrap_methods];
# }
# The items of the BootstrapMethods_attribute structure are as follows:

# attribute_name_index
# The value of the attribute_name_index item must be a valid index into the
# constant_pool table. The constant_pool entry at that index must be a
# CONSTANT_Utf8_info structure (§4.4.7) representing the string
# "BootstrapMethods".

# attribute_length
# The value of the attribute_length item indicates the length of the attribute, excluding the initial six bytes.

# The value of the attribute_length item is thus dependent on the number of
# invokedynamic instructions in this ClassFile structure.

# num_bootstrap_methods
# The value of the num_bootstrap_methods item determines the number of
# bootstrap method specifiers in the bootstrap_methods array.

# bootstrap_methods[]
# Each entry in the bootstrap_methods array contains an index to a
# CONSTANT_MethodHandle_info structure (§4.4.8) which specifies a bootstrap
# method, and a sequence (perhaps empty) of indexes to static arguments for the
# bootstrap method.

# Each bootstrap_methods entry must contain the following three items:

# bootstrap_method_ref
# The value of the bootstrap_method_ref item must be a valid index into the
# constant_pool table. The constant_pool entry at that index must be a
# CONSTANT_MethodHandle_info structure (§4.4.8).

# The reference_kind item of the CONSTANT_MethodHandle_info structure should
# have the value 6 (REF_invokeStatic) or 8 (REF_newInvokeSpecial) (§5.4.3.5) or
# else invocation of the bootstrap method handle during call site specifier
# resolution for an invokedynamic instruction will complete abruptly.

# num_bootstrap_arguments
# The value of the num_bootstrap_arguments item gives the number of items in the bootstrap_arguments array.

# bootstrap_arguments
# Each entry in the bootstrap_arguments array must be a valid index into the
# constant_pool table. The constant_pool entry at that index must be a
# CONSTANT_String_info, CONSTANT_Class_info, CONSTANT_Integer_info,
# CONSTANT_Long_info, CONSTANT_Float_info, CONSTANT_Double_info,
# CONSTANT_MethodHandle_info, or CONSTANT_MethodType_info structure (§4.4.3,
# §4.4.1, §4.4.4, §4.4.5), §4.4.8, §4.4.9).
