Пример #1
0
class ClassFile(object):
    """
    Implements the JVM ClassFile (files typically ending in ``.class``).

    To open an existing ClassFile::

        from jawa import ClassFile
        with open('HelloWorld.class') as fin:
            cf = ClassFile(fin)

    To save a newly created or modified ClassFile::

        with open('HelloWorld.class', 'wb') as fout:
            cf.save(fout)

    To create a new ClassFile, use the helper :meth:`~ClassFile.create`::

        from jawa import ClassFile
        cf = ClassFile.create('HelloWorld')
        with open('HelloWorld.class', 'wb') as fout:
            cf.save(fout)

    :meth:`~ClassFile.create` sets up some reasonable defaults equivelent to:

    .. code-block:: java

        public class HelloWorld extends java.lang.Object{
        }

    :param fio: any file-like object providing ``.read()``.
    """
    #: The JVM ClassFile magic number.
    MAGIC = 0xCAFEBABE

    def __init__(self, fio=None):
        # Default to J2SE_7
        self._version = ClassVersion(0x32, 0)
        self._constants = ConstantPool()
        self._access_flags = Flags('>H', {
            'acc_public': 0x0001,
            'acc_final': 0x0010,
            'acc_super': 0x0020,
            'acc_interface': 0x0200,
            'acc_abstract': 0x0400,
            'acc_synthetic': 0x1000,
            'acc_annotation': 0x2000,
            'acc_enum': 0x4000
        })
        self._this = 0
        self._super = 0
        self._interfaces = []
        self._fields = FieldTable(self)
        self._methods = MethodTable(self)
        self._attributes = AttributeTable(self)

        if fio:
            self._from_io(fio)

    @classmethod
    def create(cls, this, super_='java/lang/Object'):
        """
        A utility method which sets up reasonable defaults for a new public
        class.

        :param this: The name of this class.
        :param super_: The name of this class's superclass.
        """
        cf = ClassFile()
        cf.access_flags.acc_public = True
        cf.access_flags.acc_super = True

        cf._this = cf.constants.create_class(this).index
        cf._super = cf.constants.create_class(super_).index

        return cf

    def save(self, fout):
        """
        Saves the class to the file-like object `fout`.
        """
        write = fout.write

        write(pack('>IHH',
            ClassFile.MAGIC,
            self.version.minor,
            self.version.major
        ))

        self._constants._to_io(fout)

        write(self.access_flags.pack())
        write(pack('>HHH{0}H'.format(len(self._interfaces)),
            self._this,
            self._super,
            len(self._interfaces),
            *self._interfaces
        ))

        self._fields._to_io(fout)
        self._methods._to_io(fout)
        self._attributes._to_io(fout)

    # ------------
    # Internal
    # ------------

    def _from_io(self, fio):
        """
        Loads an existing JVM ClassFile from any file-like object.
        """
        read = fio.read

        if unpack('>I', fio.read(4))[0] != ClassFile.MAGIC:
            raise ValueError('invalid magic number')

        # The version is swapped on disk to (minor, major), so swap it back.
        self.version = unpack('>HH', fio.read(4))[::-1]

        self._constants._from_io(fio)

        # ClassFile access_flags, see section #4.1 of the JVM specs.
        self.access_flags.unpack(read(2))

        # The CONSTANT_Class indexes for "this" class and its superclass.
        # Interfaces are a simple list of CONSTANT_Class indexes.
        self._this, self._super, interfaces_count = unpack('>HHH', read(6))
        self._interfaces = unpack(
            '>{0}H'.format(interfaces_count),
            read(2 * interfaces_count)
        )

        self._fields._from_io(fio)
        self._methods._from_io(fio)
        self._attributes._from_io(fio)

    # -------------
    # Properties
    # -------------

    @property
    def version(self):
        """
        The :class:`~jawa.cf.ClassVersion` for this class.

        Example::

            >>> cf.version = 51, 0
            >>> print(cf.version)
            ClassVersion(major=51, minor=0)
            >>> print(cf.version.major)
            51
        """
        return self._version

    @version.setter
    def version(self, (major, minor)):
        self._version = ClassVersion(major, minor)