{
    JavaRecompiler реализует статический рекомпилятор для языка Java.
    Этот исходный текст является частью Малик Эмулятора.

    Copyright © 2016–2017, 2019–2022 Малик Разработчик

    Малик Эмулятор – свободная программа: вы можете перераспространять её и/или
    изменять её на условиях Стандартной общественной лицензии GNU в том виде,
    в каком она была опубликована Фондом свободного программного обеспечения;
    либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

    Малик Эмулятор распространяется в надежде, что он может быть полезен,
    но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
    или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЁННЫХ ЦЕЛЕЙ. Подробнее см. в Стандартной
    общественной лицензии GNU.

    Вы должны были получить копию Стандартной общественной лицензии GNU
    вместе с этой программой. Если это не так, см.
    <http://www.gnu.org/licenses/>.
}

unit JavaRecompiler;

{$MODE DELPHI}

interface

uses
    Classes,
    SysUtils,
    Graphics,
    Lang,
    IOStreams,
    FileIO,
    Zlib,
    ReadZIP,
    Manifests,
    EmulMalik,
    EmulConstants,
    StaticRecompilers,
    StringPools;

{%region public }
const
    ATTRIBUTE_CONTAINER_GUID = '{74C86B60-AC5B-4E8D-8ACD-29A50B5C1517}';
    CONSTANT_POOL_CONTAINER_GUID = '{74C86B60-AC5B-4E8D-8ACD-29A50B5C1518}';

type
    AttributeContainer = interface;
    ConstantPoolContainer = interface;
    ClassNotFoundException = class;
    ClassMemberNotFoundException = class;
    ClassFileInvalidFormatException = class;
    UnsupportedClassVersionException = class;
    UnsupportedBytecodeException = class;
    ManifestNotFoundException = class;
    ManifestPropertyNotFoundException = class;
    ConstantPoolEntry = class;
    StringEntry = class;
    IntegerEntry = class;
    LongEntry = class;
    FloatEntry = class;
    DoubleEntry = class;
    SingleIndexedEntry = class;
    DoubleIndexedEntry = class;
    ExceptionCatch = class;
    ExceptionHandler = class;
    InterfaceMethodImplements = class;
    InterfaceImplementsInfo = class;
    JavaAttribute = class;
    JavaConstantValue = class;
    JavaLineNumberTable = class;
    JavaCode = class;
    JavaClassMember = class;
    JavaField = class;
    JavaMethod = class;
    JavaClass = class;
    JavaArray = class;
    JavaStaticRecompiler = class;

    JavaAttribute_Class = class of JavaAttribute;

    ConstantPoolEntry_Array1d = packed array of ConstantPoolEntry;
    ExceptionCatch_Array1d = packed array of ExceptionCatch;
    ExceptionHandler_Array1d = packed array of ExceptionHandler;
    InterfaceMethodImplements_Array1d = packed array of InterfaceMethodImplements;
    InterfaceImplementsInfo_Array1d = packed array of InterfaceImplementsInfo;
    JavaField_Array1d = packed array of JavaField;
    JavaMethod_Array1d = packed array of JavaMethod;
    JavaClass_Array1d = packed array of JavaClass;
    JavaArray_Array1d = packed array of JavaArray;

    AttributeContainer = interface(_Interface) [ATTRIBUTE_CONTAINER_GUID]
        procedure onAddAttribute(attribute: JavaAttribute);
        function getAttributeClass(const name: AnsiString): JavaAttribute_Class;
    end;

    ConstantPoolContainer = interface(_Interface) [CONSTANT_POOL_CONTAINER_GUID]
        function getLongIndex(value: long): int;
        function getDoubleIndex(value: double): int;
        function getStringIndex(const value: AnsiString): int;
        function getConstantPoolLength(): int;
        function getConstantPoolEntry(index: int): ConstantPoolEntry;
        function getConstantPoolMember(index, memberType: int): JavaClassMember; overload;
        function getConstantPoolMember(classNameIndex, memberNameIndex, memberSignatureIndex, memberType: int): JavaClassMember; overload;
    end;

    ClassNotFoundException = class(Exception)
    public
        constructor create(); overload;
        constructor create(const message: AnsiString); overload;
    end;

    ClassMemberNotFoundException = class(Exception)
    public
        constructor create(); overload;
        constructor create(const message: AnsiString); overload;
    end;

    NativeMethodNotFoundException = class(Exception)
    public
        constructor create(); overload;
        constructor create(const message: AnsiString); overload;
    end;

    ClassFileInvalidFormatException = class(Exception)
    public
        constructor create(); overload;
        constructor create(const message: AnsiString); overload;
    end;

    UnsupportedClassVersionException = class(Exception)
    public
        constructor create(); overload;
        constructor create(const message: AnsiString); overload;
    end;

    UnsupportedBytecodeException = class(Exception)
    public
        constructor create(); overload;
        constructor create(const message: AnsiString); overload;
    end;

    ManifestNotFoundException = class(Exception)
    public
        constructor create(); overload;
        constructor create(const message: AnsiString); overload;
    end;

    ManifestPropertyNotFoundException = class(Exception)
    public
        constructor create(); overload;
        constructor create(const message: AnsiString); overload;
    end;

    ConstantPoolEntry = class(_Object)
    public
        constructor create();
    end;

    StringEntry = class(ConstantPoolEntry)
    strict private
        value: AnsiString;
    public
        constructor create(const value: AnsiString);
        function stringValue(): AnsiString;
    end;

    IntegerEntry = class(ConstantPoolEntry)
    strict private
        value: int;
    public
        constructor create(value: int);
        function intValue(): int;
    end;

    LongEntry = class(ConstantPoolEntry)
    strict private
        value: long;
    public
        constructor create(value: long);
        function longValue(): long;
    end;

    FloatEntry = class(ConstantPoolEntry)
    strict private
        value: float;
    public
        constructor create(value: float);
        function floatValue(): float;
    end;

    DoubleEntry = class(ConstantPoolEntry)
    strict private
        value: double;
    public
        constructor create(value: double);
        function doubleValue(): double;
    end;

    SingleIndexedEntry = class(ConstantPoolEntry)
    strict private
        entryType: int;
        index1: int;
    public
        constructor create(entryType, index1: int);
        function getEntryType(): int;
        function getIndex1(): int;
    end;

    DoubleIndexedEntry = class(SingleIndexedEntry)
    strict private
        index2: int;
    public
        constructor create(entryType, index1, index2: int);
        function getIndex2(): int;
    end;

    ExceptionCatch = class(_Object)
    strict private
        catchOffset: int;
        classIndex: int;
        commandIndex: int;
    private
        function getCommandIndex(): int;
        procedure setCommandIndex(commandIndex: int);
    public
        constructor create(catchOffset, classIndex: int);
        function getCatchOffset(): int;
        function getClassIndex(): int;
    end;

    ExceptionHandler = class(_Object)
    strict private
        tryStart: int;
        tryFinish: int;
        catchesLength: int;
        catches: ExceptionCatch_Array1d;
    public
        constructor create(tryStart, tryFinish: int);
        destructor destroy; override;
        function getTryStart(): int;
        function getTryFinish(): int;
        function getCatchesLength(): int;
        function getCatch(index: int): ExceptionCatch;
        procedure addCatch(catchOffset, classIndex: int);
    end;

    InterfaceMethodImplements = class(_Object)
    strict private
        interfaceMethod: JavaMethod;
        classMethod: JavaMethod;
    private
        procedure setClassMethod(classMethod: JavaMethod);
    public
        constructor create(interfaceMethod: JavaMethod);
        function getMethodName(): AnsiString;
        function getMethodSignature(): AnsiString;
        function getInterfaceMethod(): JavaMethod;
        function getClassMethod(): JavaMethod;
    end;

    InterfaceImplementsInfo = class(_Object)
    strict private
        name: AnsiString;
        intf: JavaClass;
        imtOffset: int;
        methods: InterfaceMethodImplements_Array1d;
    private
        procedure setInterface(intf: JavaClass);
        procedure setIMTOffset(imtOffset: int);
    public
        constructor create(const interfaceName: AnsiString); overload;
        constructor create(intf: JavaClass); overload;
        destructor destroy; override;
        function getInterfaceName(): AnsiString;
        function getInterface(): JavaClass;
        function getIMTOffset(): int;
        function getMethodsCount(): int;
        function getMethodImplements(index: int): InterfaceMethodImplements;
    end;

    JavaAttribute = class(_Object)
    strict private
        owner: AttributeContainer;
        content: byte_Array1d;
        name: AnsiString;
    private
        function byteAt(offset: int): int;
        function shortAt(offset: int): int;
        function intAt(offset: int): int;
        function unsignedByteAt(offset: int): int;
        function unsignedShortAt(offset: int): int;
    public
        constructor create(owner: AttributeContainer; const name: AnsiString; const content: byte_Array1d; const constantPool: ConstantPoolEntry_Array1d); virtual;
        function getOwner(): AttributeContainer;
        function getSize(): int;
        function getName(): AnsiString;

    public
        class procedure readAttributes(container: AttributeContainer; stream: DataInput; const constantPool: ConstantPoolEntry_Array1d);
    end;

    JavaConstantValue = class(JavaAttribute)
    strict private
        constantPoolIndex: int;
    public
        constructor create(owner: AttributeContainer; const name: AnsiString; const content: byte_Array1d; const constantPool: ConstantPoolEntry_Array1d); override;
        function getConstantPoolIndex(): int;
    end;

    JavaLineNumberTable = class(JavaAttribute)
    strict private
        linesCount: int;
    public
        constructor create(owner: AttributeContainer; const name: AnsiString; const content: byte_Array1d; const constantPool: ConstantPoolEntry_Array1d); override;
        function getLinesCount(): int;
        function getLineNumber(lineIndex: int): int;
        function getCodeOffsetAt(lineIndex: int): int;
    end;

    JavaCode = class(JavaAttribute, AttributeContainer)
    strict private
        registersCount: int;
        variablesCount: int;
        exceptionHandlersCount: int;
        exceptionHandlers: ExceptionHandler_Array1d;
        instructionOffsets: int_Array1d;
        trueRegistersIndices: int_Array1d;
        lineNumberTable: JavaLineNumberTable;
        procedure addExceptionHandler(tryStart, tryFinish, catchOffset, classIndex: int);
        procedure sortHandlers();
        procedure fillInstructionOffsets();
        procedure fillTrueRegistersIndices();
        function getRegisterSize(registerIndex: int): int;
    public
        constructor create(owner: AttributeContainer; const name: AnsiString; const content: byte_Array1d; const constantPool: ConstantPoolEntry_Array1d); override;
        destructor destroy; override;
        procedure onAddAttribute(attribute: JavaAttribute);
        function getAttributeClass(const name: AnsiString): JavaAttribute_Class;
        function getInstructionsCount(): int;
        function getInstructionOffset(instructionIndex: int): int;
        function getTrueRegisterIndex(javaRegisterIndex: int): int;
        function getExceptionHandlersCount(): int;
        function getExceptionHandler(index: int): ExceptionHandler;
        function getVariablesCount(): int;
        function getLineNumberTable(): JavaLineNumberTable;
        function indexOfInstruction(instructionOffset: int): int;

    strict private
        class function extractCode(const content: byte_Array1d): byte_Array1d;
    end;

    JavaClassMember = class(_Object, AttributeContainer)
    strict private
        owner: JavaClass;
        offset: int;
        flags: int;
        name: AnsiString;
        signature: AnsiString;
    private
        procedure setOffset(offset: int);
    public
        constructor create(owner: JavaClass; flags: int; const name, signature: AnsiString);
        procedure onAddAttribute(attribute: JavaAttribute); virtual; abstract;
        function getAttributeClass(const name: AnsiString): JavaAttribute_Class; virtual; abstract;
        function getCompiler(): JavaStaticRecompiler; virtual;
        function getOwner(): JavaClass;
        function getOffset(): int;
        function getFlags(): int;
        function getName(): AnsiString;
        function getSignature(): AnsiString;
        function getAccess(): int;
        function isStatic(): boolean;
        function isFinal(): boolean;
    end;

    JavaField = class(JavaClassMember)
    strict private
        constantValue: JavaConstantValue;
        outToExecutable: boolean;
    public
        constructor create(owner: JavaClass; flags: int; const name, signature: AnsiString);
        destructor destroy; override;
        procedure onAddAttribute(attribute: JavaAttribute); override;
        function getAttributeClass(const name: AnsiString): JavaAttribute_Class; override;
        function getConstantPoolIndex(): int;
        function isOutToExecutable(): boolean;
    end;

    JavaMethod = class(JavaClassMember)
    strict private
        parametersCount: int;
        virtualStatus: int;
        virtualIndex: int;
        commandIndices: int_Array1d;
        commandsCount: int;
        commands: long_Array1d;
        overridden: JavaMethod;
        sourceCode: JavaCode;
        procedure generateSyscall();
        procedure generateGetVariableAddress();
        procedure generateVoidInterrupt();
        procedure generateIntInterrupt();
        procedure generateLongInterrupt();
        procedure generateObjectInterrupt();
        procedure generateRunExcept();
        procedure generateGetFloatAt();
        procedure generateGetDoubleAt();
        procedure generateGetByteAt();
        procedure generateGetShortAt();
        procedure generateGetIntAt();
        procedure generateGetLongAt();
        procedure generateGetObjectAt();
        procedure generateSetFloatAt();
        procedure generateSetDoubleAt();
        procedure generateSetByteAt();
        procedure generateSetShortAt();
        procedure generateSetIntAt();
        procedure generateSetLongAt();
        procedure generateSetObjectAt();
        procedure generateGetCurrentThreadID();
        procedure generateGetReturnAddress();
        procedure generateConvertToReference();
        procedure generateConvertToObject();
        procedure generateGetMethodAddress();
        procedure generateGetClassInstance();
        procedure generateArrayCopy(direction: boolean; dataType: int);
        procedure generateArrayFind(direction: boolean; dataType: int);
        procedure generateArrayFill(dataType: int);
        procedure generateFind(direction, zero: boolean);
        procedure generateGetObjectRefs();
        procedure generateGetArrayRefs();
        procedure generateBlockFindF();
        procedure generateBlockFindB();
        procedure generateInvokeDefaultConstructor();
        procedure generateIntPart();
        procedure generateFracPart();
        procedure generateSqrt();
        procedure generateArctan();
        procedure generateSin();
        procedure generateCos();
        procedure generatePow2();
        procedure generateLog2();
        procedure generateFloor();
        procedure generateCeil();
        procedure generateRound();
        procedure defineVirtualStatus();
        procedure addCommandFull(cmd: long);
        procedure addCommand(); overload;
        procedure addCommand(opcode: int); overload;
        procedure addCommand(opcode, data: int); overload;
        procedure addCommand(opcode, classIndex, nameIndex, signatureIndex: int); overload;
        procedure transformParameters(const signature: AnsiString);
        procedure transformReturnValue(const signature: AnsiString);
        procedure insertNativeCode(nativeIndex: int); overload;
        procedure insertNativeCode(nativeIndex, parameter: int); overload;
        function getNativeMethodIndex(pc: int): int;
        function getVariableOffset(javaRegisterIndex: int): int;
    private
        procedure setVirtualIndex(virtualIndex: int);
        procedure prepareCompile();
        procedure fillStringPool();
        procedure writeCode(stream: DataOutput);
        function reduceSizeofJumps(): boolean;
        function reduceSizeofCalls(): boolean;
        function getRelativeOffset(javaInstructionOffset: int): int;
    public
        constructor create(owner: JavaClass; flags: int; const name, signature: AnsiString);
        destructor destroy; override;
        procedure onAddAttribute(attribute: JavaAttribute); override;
        function getAttributeClass(const name: AnsiString): JavaAttribute_Class; override;
        function getCodeSize(): int;
        function getParametersCount(): int;
        function getVirtualIndex(): int;
        function getOverridden(): JavaMethod;
        function getLineNumberTable(): JavaLineNumberTable;
        function isSynchronized(): boolean;
        function isStrictfp(): boolean;
        function isAbstract(): boolean;
        function isVirtual(): boolean;
        function isNative(): boolean;

    strict private const
        VIRTUAL_STATUS_NOT_DEFINED = int(0);
        VIRTUAL_STATUS_HAS_VIRTUAL = int(1);
        VIRTUAL_STATUS_NOT_VIRTUAL = int(2);
    end;

    JavaClass = class(JavaClassMember, ConstantPoolContainer)
    strict private
        owner: JavaStaticRecompiler;
        packageName: AnsiString;
        outputName: AnsiString;
        ancestorName: AnsiString;
        ancestor: JavaClass;
        next: JavaClass;
        constantPoolLength: int;
        constantPool: ConstantPoolEntry_Array1d;
        interfacesCount: int;
        interfaces: InterfaceImplementsInfo_Array1d;
        fieldsCount: int;
        fields: JavaField_Array1d;
        methodsCount: int;
        methods: JavaMethod_Array1d;
        sourceFile: JavaConstantValue;
        instanceSize: int;
        virtualsCount: int;
        nameIndex: int;
        procedure addMethod(method: JavaMethod);
        procedure addInterface(intf: JavaClass);
        function findVirtualMethod(virtualIndex: int): JavaMethod;
        function getRefFieldsOffsets(): int_Array1d;
    private
        procedure findAncestors();
        procedure defineImplementedMethods();
        procedure defineVirtualMethods();
        procedure defineFieldOffsets();
        procedure writeIMTs(stream: DataOutput);
        procedure writeData(stream: DataOutput);
        procedure setMembers(const fields: JavaField_Array1d; const methods: JavaMethod_Array1d);
        procedure setNameIndex(nameIndex: int);
        procedure setNextClass(next: JavaClass);
        procedure setIMTOffset(interfaceIndex, imtOffset: int);
        function getIMTOffset(interfaceIndex: int): int;
        function findMember(const name, signature: AnsiString): JavaClassMember;
        function findMemberInternal(const name, signature: AnsiString): JavaClassMember;
    public
        constructor create(owner: JavaStaticRecompiler; const constantPool: ConstantPoolEntry_Array1d; flags: int; const name, ancestor: AnsiString; const interfaces: AnsiString_Array1d); overload;
        constructor create(owner: JavaStaticRecompiler; nameIndex: int); overload;
        destructor destroy; override;
        procedure onAddAttribute(attribute: JavaAttribute); override;
        function getAttributeClass(const name: AnsiString): JavaAttribute_Class; override;
        function getCompiler(): JavaStaticRecompiler; override;
        function getComponentClass(): JavaClass; virtual;
        function getInstanceSize(): int; virtual;
        function getLongIndex(value: long): int;
        function getDoubleIndex(value: double): int;
        function getStringIndex(const value: AnsiString): int;
        function getConstantPoolLength(): int;
        function getConstantPoolEntry(index: int): ConstantPoolEntry;
        function getConstantPoolMember(index, memberType: int): JavaClassMember; overload;
        function getConstantPoolMember(classNameIndex, memberNameIndex, memberSignatureIndex, memberType: int): JavaClassMember; overload;
        function getPackageName(): AnsiString;
        function getOutputName(): AnsiString;
        function getAncestor(): JavaClass;
        function getInterfacesCount(): int;
        function getInterfaceImplements(index: int): InterfaceImplementsInfo;
        function getInterface(index: int): JavaClass;
        function getAbstractMethodsCount(): int;
        function getMethodsCount(): int;
        function getMethod(index: int): JavaMethod;
        function getFieldsCount(): int;
        function getField(index: int): JavaField;
        function getSourceFile(): AnsiString;
        function getDataSize(): int;
        function isDirectSuperClass(ancestor: JavaClass): boolean;
        function isInheritedFrom(cls: JavaClass): boolean;
        function isPrimitive(): boolean;
        function isInterface(): boolean;
        function isAbstract(): boolean;

    private
        class procedure writeClassReference(stream: DataOutput; cls: JavaClass);
        class function getClassSignature(const name: AnsiString): AnsiString;
    end;

    JavaArray = class(JavaClass)
    strict private
        componentClass: JavaClass;
        cellClass: JavaClass;
        dimensionsCount: int;
    public
        constructor create(owner: JavaStaticRecompiler; componentClass: JavaClass);
        function getComponentClass(): JavaClass; override;
        function getInstanceSize(): int; override;
        function getCellClass(): JavaClass;
        function getDimensionsCount(): int;
    end;

    JavaStaticRecompiler = class(RefCountInterfacedObject, StaticRecompiler, AdjustableRecompiler, ConstantPoolContainer)
    strict private
        coreDirectory: AnsiString;
        platformName: AnsiString;
        primitives: JavaClass_Array1d;
        fdconst: ConstantPoolEntry_Array1d;
    strict private
        classesCount: int;
        classes: JavaClass_Array1d;
        arraysCount: int;
        arrays: JavaArray_Array1d;
        entryCommands: long_Array1d;
        initOrder: StringPool;
        strings: StringPool;
    strict private
        imageBeginOffset: int;
        methodsOffset: int;
        entryOffset: int;
        stringsOffset: int;
        imageEndOffset: int;
        availableMemoryInMB: int;
    strict private
        outStrings: boolean;
        alignment: boolean;
        compressionLevel: int;
        stackSizeInMB: int;
        heapSize: int;
        descSize: int;
        executableFileName: AnsiString;
    strict private
        procedure init();
        procedure loadClassesFrom(archive: ZIPArchiveReader);
        procedure loadClass(source: Input);
        procedure addArray(arr: JavaArray);
        procedure findAncestors();
        procedure findSystemMembers();
        procedure sortClasses();
        procedure preferenceForClassMembers();
        procedure prepareMethods();
        procedure prepareEntry();
        procedure linkClasses();
        procedure extractStrings();
        procedure assignOffsets();
        procedure reduceCodeSize();
        procedure computeAvailableMemory();
        procedure createExecutableFile(const fileName: AnsiString);
        procedure createTextDebugInfoFile(const fileName: AnsiString);
        procedure createBinDebugInfoFile(const fileName: AnsiString);
        procedure createStringPoolFile(const fileName: AnsiString);
        procedure buildMalikManifest(const outputDirectory: AnsiString; javaManifest, malikManifest: ProgrammeManifest);
        procedure clear();
        function isJarArchiveForEmulator(archive: ZIPArchiveReader): boolean;
        function indexOfArray(const name: AnsiString): int;
        function getArray(const name: AnsiString): JavaArray;
        function findSystemMember(desriptor: _Object): JavaClassMember;
    private
        classObject: JavaClass;
        classClass: JavaClass;
        classInterfaceEntry: JavaClass;
        classMalikSystem: JavaClass;
        classMath: JavaClass;
        interfaceInterrupt: JavaClass;
        methodDefaultConstructor: JavaClassMember;
        methodGetString: JavaClassMember;
        methodCheckObjectArrayAssignable: JavaClassMember;
        methodGetInterfaceMethodEntryPoint: JavaClassMember;
        methodAllocateInstance: JavaClassMember;
        methodArrayCreateOneDim: JavaClassMember;
        methodArrayCreateMultiDim: JavaClassMember;
        methodCast: JavaClassMember;
        methodIsInstance: JavaClassMember;
        methodMonitorEnter: JavaClassMember;
        methodMonitorExit: JavaClassMember;
        methodThrowAbstractMethodError: JavaClassMember;
        methodMain: JavaClassMember;
        procedure writeCode(stream: DataOutput; const commands: long_Array1d; commandsCount: int; offset: int; owner: ConstantPoolContainer);
        function reduceSizeofJumps(const commands: long_Array1d; commandsCount: int): boolean;
        function reduceSizeofCalls(const commands: long_Array1d; commandsCount: int; offset: int; owner: ConstantPoolContainer): boolean;
        function getStringPoolStringIndex(const str: AnsiString): int;
        function getClassesCount(): int;
        function getClass(index: int): JavaClass; overload;
        function getTypeSize(const signature: AnsiString): int; overload;
        function getTypeSize(const signature: AnsiString; alignment: boolean): int; overload;
        function findClassByName(const name: AnsiString): JavaClass;
        function findClassBySignature(const signature: AnsiString): JavaClass;
    public
        constructor create(); overload;
        constructor create(const coreDirectory, platformName: AnsiString); overload;
        destructor destroy; override;
        { StaticRecompiler }
        function createInstance(): StaticRecompiler;
        function install(const archive, destinationDirectory: AnsiString): AnsiString;
        function getPlatformName(): AnsiString;
        function getArchiveTypeName(): AnsiString;
        function getArchiveExtension(): AnsiString;
        { AdjustableRecompiler }
        function getAlignment(): boolean;
        function getCompressionLevel(): int;
        function getStackSize(): int;
        function getHeapSize(): int;
        function getDescriptorsSize(): int;
        function getOutputExecutableFileName(): AnsiString;
        procedure setDefaults();
        procedure setAlignment(alignment: boolean);
        procedure setCompressionLevel(compressionLevel: int);
        procedure setStackSize(sizeInMB: int);
        procedure setHeapSize(sizeInKB: int);
        procedure setDescriptorsSize(sizeInKB: int);
        procedure setOutputExecutableFileName(const relativeFileName: AnsiString);
        { ConstantPoolContainer }
        function getLongIndex(value: long): int;
        function getDoubleIndex(value: double): int;
        function getStringIndex(const value: AnsiString): int;
        function getConstantPoolLength(): int;
        function getConstantPoolEntry(index: int): ConstantPoolEntry;
        function getConstantPoolMember(index, memberType: int): JavaClassMember; overload;
        function getConstantPoolMember(classNameIndex, memberNameIndex, memberSignatureIndex, memberType: int): JavaClassMember; overload;

    strict private
        procedure writeToTextDebugInfoFile(stream: DataOutput; cls: JavaClass);
    end;
{%endregion}

implementation

{%region private }
const
    ENTRY_UTF8 = 1;
    ENTRY_INTEGER = 3;
    ENTRY_FLOAT = 4;
    ENTRY_LONG = 5;
    ENTRY_DOUBLE = 6;
    ENTRY_CLASS = 7;
    ENTRY_STRING = 8;
    ENTRY_FIELD_REF = 9;
    ENTRY_CLASS_METHOD_REF = 10;
    ENTRY_INTERFACE_METHOD_REF = 11;
    ENTRY_NAME_AND_SIGNATURE = 12;

const
    ACCESS_PRIVATE = int(0);
    ACCESS_PACKAGED = int(1);
    ACCESS_PROTECTED = int(2);
    ACCESS_PUBLIC = int(3);

const
    FLAG_PUBLIC = int($0001);
    FLAG_PRIVATE = int($0002);
    FLAG_PROTECTED = int($0004);
    FLAG_STATIC = int($0008);
    FLAG_FINAL = int($0010);
    FLAG_SYNCHRONIZED = int($0020);
    { FLAG_VOLATILE = int($0040); // не используется }
    { FLAG_TRANSIENT = int($0080); // не используется }
    FLAG_NATIVE = int($0100);
    FLAG_INTERFACE = int($0200);
    FLAG_ABSTRACT = int($0400);
    FLAG_STRICTFP = int($0800);
    FLAG_PRIMITIVE = int($00010000);

const
    PREFIX_VOID = 'V';
    PREFIX_BOOLEAN = 'Z';
    PREFIX_CHAR = 'C';
    PREFIX_FLOAT = 'F';
    PREFIX_DOUBLE = 'D';
    PREFIX_BYTE = 'B';
    PREFIX_SHORT = 'S';
    PREFIX_INT = 'I';
    PREFIX_LONG = 'J';
    PREFIX_ARRAY = '[';
    PREFIX_OBJECT = 'L';
    SUFFIX_OBJECT = ';';
    HEADER_PREFIX = '(';
    HEADER_SUFFIX = ')';

const
    SEPARATOR_PACKAGES = '/';
    SEPARATOR_CANONICAL = '.';

const
    MEMBER_TYPE_STATIC_FIELD = int(0);
    MEMBER_TYPE_INSTANCE_FIELD = int(1);
    MEMBER_TYPE_STATIC_METHOD = int(2);
    MEMBER_TYPE_INSTANCE_METHOD = int(3);
    MEMBER_TYPE_INTERFACE_METHOD = int(4);

const
    IMT_ENTRY_SIZE = int($10);

const
    CLASS_FILE_EXTENSION = '.class';
    MANIFEST_DIRECTORY = 'META-INF';
    MANIFEST_FILE_NAME = MANIFEST_DIRECTORY + '/MANIFEST.MF';
    MANIFEST_FILE_PLATFORM_NAME = MANIFEST_DIRECTORY + DIRECTORY_SEPARATOR + 'MANIFEST.MF';
    PACKAGE_CORE = 'java' + SEPARATOR_PACKAGES + 'lang' + SEPARATOR_PACKAGES;
    PACKAGE_MAIN = 'malik' + SEPARATOR_PACKAGES + 'emulator' + SEPARATOR_PACKAGES + 'application' + SEPARATOR_PACKAGES;
    CLASS_OBJECT = PACKAGE_CORE + 'Object';
    CLASS_CLASS = PACKAGE_CORE + 'Class';
    CLASS_INTERFACE_ENTRY = PACKAGE_CORE + 'InterfaceEntry';
    CLASS_MALIK_SYSTEM = PACKAGE_CORE + 'MalikSystem';
    CLASS_MATH = PACKAGE_CORE + 'Math';
    CLASS_MEMORY = PACKAGE_CORE + 'Memory';
    CLASS_ARRAY = PACKAGE_CORE + 'Array';
    CLASS_STRING = PACKAGE_CORE + 'String';
    CLASS_STRING_POOL = PACKAGE_CORE + 'StringPool';
    CLASS_SYSTEM = PACKAGE_CORE + 'System';
    CLASS_RUNTIME = PACKAGE_CORE + 'Runtime';
    CLASS_THREAD = PACKAGE_CORE + 'Thread';
    CLASS_THROWABLE = PACKAGE_CORE + 'Throwable';
    INTERFACE_INTERRUPT = PACKAGE_CORE + 'Interrupt';
    CLASS_RUN = PACKAGE_MAIN + 'Run';

const
    PROGRAMME_ENTRY = 'programme entry';
    UNKNOWN_SOURCE = 'Unknown Source';
    NAME_INITIALIZATION = '<clinit>';
    NAME_CONSTRUCTOR = '<init>';
    NAME_FINALIZE = '$finalize$';
    NAME_INTERRUPT = 'interrupt';
    NAME_CURRENT_THREAD = 'currentThread';
    SIGNATURE_NO_PARAMETERS = HEADER_PREFIX + HEADER_SUFFIX + PREFIX_VOID;

type
    ClassMemberDescriptor = class;

    ClassMemberDescriptor_Array1d = packed array of ClassMemberDescriptor;

    ClassMemberDescriptor = class(_Object)
    strict private
        className: AnsiString;
        memberName: AnsiString;
        signature: AnsiString;
    public
        constructor create(const className, memberName, signature: AnsiString);
        function getClassName(): AnsiString;
        function getMemberName(): AnsiString;
        function getSignature(): AnsiString;
        function equals(member: JavaClassMember): boolean; overload;
    end;

var
    PRIMITIVES_NAMES: AnsiString_Array1d;
    PRIMITIVES_PREFIXES: AnsiString_Array1d;
    MEMBER_TYPES: AnsiString_Array1d;
    MEMBER_DEFAULT_CONSTRUCTOR: ClassMemberDescriptor;
    MEMBER_STRING_POOL_OFFSET: ClassMemberDescriptor;
    MEMBER_DESCRIPTORS_SIZE: ClassMemberDescriptor;
    MEMBER_HEAP_LIMIT: ClassMemberDescriptor;
    MEMBER_MONITOR_ENTER: ClassMemberDescriptor;
    MEMBER_MONITOR_EXIT: ClassMemberDescriptor;
    MEMBER_CHECK_OBJECT_ARRAY_ASSIGNABLE: ClassMemberDescriptor;
    MEMBER_GET_INTERFACE_METHOD_ENTRY_POINT: ClassMemberDescriptor;
    MEMBER_ALLOCATE_INSTANCE: ClassMemberDescriptor;
    MEMBER_IS_INSTANCE: ClassMemberDescriptor;
    MEMBER_CAST: ClassMemberDescriptor;
    MEMBER_GET_STRING: ClassMemberDescriptor;
    MEMBER_ARRAY_CREATE_ONE_DIM: ClassMemberDescriptor;
    MEMBER_ARRAY_CREATE_MULTI_DIM: ClassMemberDescriptor;
    MEMBER_THROW_ABSTRACT: ClassMemberDescriptor;
    MEMBER_MAIN_PROCEDURE: ClassMemberDescriptor;
    NATIVE_METHODS: ClassMemberDescriptor_Array1d;
{%endregion}

{%region routine }
    function ConstantPoolEntry_Array1d_create(length: int): ConstantPoolEntry_Array1d;
    begin
        setLength(result, length);
    end;

    function toConstantPoolEntryArray1d(const arr: array of ConstantPoolEntry): ConstantPoolEntry_Array1d;
    begin
        setLength(result, length(arr));
        move(arr[0], result[0], length(result) * sizeof(_Object));
    end;

    procedure arraycopy(const src: ConstantPoolEntry_Array1d; srcOffset: int; const dst: ConstantPoolEntry_Array1d; dstOffset: int; length: int); overload;
    var
        lim: int;
        len: int;
    begin
        lim := srcOffset + length;
        len := System.length(src);
        if (lim > len) or (lim < srcOffset) or (srcOffset < 0) or (srcOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        lim := dstOffset + length;
        len := System.length(dst);
        if (lim > len) or (lim < dstOffset) or (dstOffset < 0) or (dstOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        move(src[srcOffset], dst[dstOffset], length * sizeof(_Object));
    end;

    function ExceptionCatch_Array1d_create(length: int): ExceptionCatch_Array1d;
    begin
        setLength(result, length);
    end;

    function toExceptionCatchArray1d(const arr: array of ExceptionCatch): ExceptionCatch_Array1d;
    begin
        setLength(result, length(arr));
        move(arr[0], result[0], length(result) * sizeof(_Object));
    end;

    procedure arraycopy(const src: ExceptionCatch_Array1d; srcOffset: int; const dst: ExceptionCatch_Array1d; dstOffset: int; length: int); overload;
    var
        lim: int;
        len: int;
    begin
        lim := srcOffset + length;
        len := System.length(src);
        if (lim > len) or (lim < srcOffset) or (srcOffset < 0) or (srcOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        lim := dstOffset + length;
        len := System.length(dst);
        if (lim > len) or (lim < dstOffset) or (dstOffset < 0) or (dstOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        move(src[srcOffset], dst[dstOffset], length * sizeof(_Object));
    end;

    function ExceptionHandler_Array1d_create(length: int): ExceptionHandler_Array1d;
    begin
        setLength(result, length);
    end;

    function toExceptionHandlerArray1d(const arr: array of ExceptionHandler): ExceptionHandler_Array1d;
    begin
        setLength(result, length(arr));
        move(arr[0], result[0], length(result) * sizeof(_Object));
    end;

    procedure arraycopy(const src: ExceptionHandler_Array1d; srcOffset: int; const dst: ExceptionHandler_Array1d; dstOffset: int; length: int); overload;
    var
        lim: int;
        len: int;
    begin
        lim := srcOffset + length;
        len := System.length(src);
        if (lim > len) or (lim < srcOffset) or (srcOffset < 0) or (srcOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        lim := dstOffset + length;
        len := System.length(dst);
        if (lim > len) or (lim < dstOffset) or (dstOffset < 0) or (dstOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        move(src[srcOffset], dst[dstOffset], length * sizeof(_Object));
    end;

    function InterfaceMethodImplements_Array1d_create(length: int): InterfaceMethodImplements_Array1d;
    begin
        setLength(result, length);
    end;

    function toInterfaceMethodImplementsArray1d(const arr: array of InterfaceMethodImplements): InterfaceMethodImplements_Array1d;
    begin
        setLength(result, length(arr));
        move(arr[0], result[0], length(result) * sizeof(_Object));
    end;

    procedure arraycopy(const src: InterfaceMethodImplements_Array1d; srcOffset: int; const dst: InterfaceMethodImplements_Array1d; dstOffset: int; length: int); overload;
    var
        lim: int;
        len: int;
    begin
        lim := srcOffset + length;
        len := System.length(src);
        if (lim > len) or (lim < srcOffset) or (srcOffset < 0) or (srcOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        lim := dstOffset + length;
        len := System.length(dst);
        if (lim > len) or (lim < dstOffset) or (dstOffset < 0) or (dstOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        move(src[srcOffset], dst[dstOffset], length * sizeof(_Object));
    end;

    function InterfaceImplementsInfo_Array1d_create(length: int): InterfaceImplementsInfo_Array1d;
    begin
        setLength(result, length);
    end;

    function toInterfaceImplementsInfoArray1d(const arr: array of InterfaceImplementsInfo): InterfaceImplementsInfo_Array1d;
    begin
        setLength(result, length(arr));
        move(arr[0], result[0], length(result) * sizeof(_Object));
    end;

    procedure arraycopy(const src: InterfaceImplementsInfo_Array1d; srcOffset: int; const dst: InterfaceImplementsInfo_Array1d; dstOffset: int; length: int); overload;
    var
        lim: int;
        len: int;
    begin
        lim := srcOffset + length;
        len := System.length(src);
        if (lim > len) or (lim < srcOffset) or (srcOffset < 0) or (srcOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        lim := dstOffset + length;
        len := System.length(dst);
        if (lim > len) or (lim < dstOffset) or (dstOffset < 0) or (dstOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        move(src[srcOffset], dst[dstOffset], length * sizeof(_Object));
    end;

    function JavaField_Array1d_create(length: int): JavaField_Array1d;
    begin
        setLength(result, length);
    end;

    function toJavaFieldArray1d(const arr: array of JavaField): JavaField_Array1d;
    begin
        setLength(result, length(arr));
        move(arr[0], result[0], length(result) * sizeof(_Object));
    end;

    procedure arraycopy(const src: JavaField_Array1d; srcOffset: int; const dst: JavaField_Array1d; dstOffset: int; length: int); overload;
    var
        lim: int;
        len: int;
    begin
        lim := srcOffset + length;
        len := System.length(src);
        if (lim > len) or (lim < srcOffset) or (srcOffset < 0) or (srcOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        lim := dstOffset + length;
        len := System.length(dst);
        if (lim > len) or (lim < dstOffset) or (dstOffset < 0) or (dstOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        move(src[srcOffset], dst[dstOffset], length * sizeof(_Object));
    end;

    function JavaMethod_Array1d_create(length: int): JavaMethod_Array1d;
    begin
        setLength(result, length);
    end;

    function toJavaMethodArray1d(const arr: array of JavaMethod): JavaMethod_Array1d;
    begin
        setLength(result, length(arr));
        move(arr[0], result[0], length(result) * sizeof(_Object));
    end;

    procedure arraycopy(const src: JavaMethod_Array1d; srcOffset: int; const dst: JavaMethod_Array1d; dstOffset: int; length: int); overload;
    var
        lim: int;
        len: int;
    begin
        lim := srcOffset + length;
        len := System.length(src);
        if (lim > len) or (lim < srcOffset) or (srcOffset < 0) or (srcOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        lim := dstOffset + length;
        len := System.length(dst);
        if (lim > len) or (lim < dstOffset) or (dstOffset < 0) or (dstOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        move(src[srcOffset], dst[dstOffset], length * sizeof(_Object));
    end;

    function JavaClass_Array1d_create(length: int): JavaClass_Array1d;
    begin
        setLength(result, length);
    end;

    function toJavaClassArray1d(const arr: array of JavaClass): JavaClass_Array1d;
    begin
        setLength(result, length(arr));
        move(arr[0], result[0], length(result) * sizeof(_Object));
    end;

    procedure arraycopy(const src: JavaClass_Array1d; srcOffset: int; const dst: JavaClass_Array1d; dstOffset: int; length: int); overload;
    var
        lim: int;
        len: int;
    begin
        lim := srcOffset + length;
        len := System.length(src);
        if (lim > len) or (lim < srcOffset) or (srcOffset < 0) or (srcOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        lim := dstOffset + length;
        len := System.length(dst);
        if (lim > len) or (lim < dstOffset) or (dstOffset < 0) or (dstOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        move(src[srcOffset], dst[dstOffset], length * sizeof(_Object));
    end;

    function JavaArray_Array1d_create(length: int): JavaArray_Array1d;
    begin
        setLength(result, length);
    end;

    function toJavaArrayArray1d(const arr: array of JavaArray): JavaArray_Array1d;
    begin
        setLength(result, length(arr));
        move(arr[0], result[0], length(result) * sizeof(_Object));
    end;

    procedure arraycopy(const src: JavaArray_Array1d; srcOffset: int; const dst: JavaArray_Array1d; dstOffset: int; length: int); overload;
    var
        lim: int;
        len: int;
    begin
        lim := srcOffset + length;
        len := System.length(src);
        if (lim > len) or (lim < srcOffset) or (srcOffset < 0) or (srcOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        lim := dstOffset + length;
        len := System.length(dst);
        if (lim > len) or (lim < dstOffset) or (dstOffset < 0) or (dstOffset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('arraycopy: индекс элемента массива выходит из диапазона.');
        end;
        move(src[srcOffset], dst[dstOffset], length * sizeof(_Object));
    end;

    function ClassMemberDescriptor_Array1d_create(length: int): ClassMemberDescriptor_Array1d;
    begin
        setLength(result, length);
    end;

    function toClassMemberDescriptorArray1d(const arr: array of ClassMemberDescriptor): ClassMemberDescriptor_Array1d;
    begin
        setLength(result, length(arr));
        move(arr[0], result[0], length(result) * sizeof(_Object));
    end;

    procedure freeClassMemberDescriptors(const descriptors: ClassMemberDescriptor_Array1d);
    var
        i: int;
    begin
        for i := length(descriptors) - 1 downto 0 do begin
            descriptors[i].free();
        end;
    end;

    function computeParametersCount(staticMethod: boolean; const signature: AnsiString): int;
    var
        i: int;
        len: int;
    begin
        if staticMethod then begin
            result := 0;
        end else begin
            result := 1;
        end;
        len := length(signature);
        if (len > 0) and (signature[1] = HEADER_PREFIX) then begin
            i := 2;
            while i <= len do begin
                case signature[i] of
                HEADER_SUFFIX: begin
                    break;
                end;
                PREFIX_BOOLEAN, PREFIX_CHAR, PREFIX_FLOAT, PREFIX_DOUBLE, PREFIX_BYTE, PREFIX_SHORT, PREFIX_INT, PREFIX_LONG: begin
                    inc(result);
                end;
                PREFIX_OBJECT: begin
                    repeat
                        inc(i);
                    until (i >= len) or (signature[i] = SUFFIX_OBJECT);
                    inc(result);
                end;
                PREFIX_ARRAY: begin
                    repeat
                        inc(i);
                    until (i >= len) or (signature[i] <> PREFIX_ARRAY);
                    if i > len then begin
                        break;
                    end;
                    if signature[i] = PREFIX_OBJECT then begin
                        repeat
                            inc(i);
                        until (i >= len) or (signature[i] = SUFFIX_OBJECT);
                    end;
                    inc(result);
                end;
                end;
                inc(i);
            end;
        end;
    end;

    function computeCodeSize(const commands: long_Array1d; offset, count: int): int;
    var
        i: int;
        f: boolean;
    begin
        f := false;
        if count < 0 then begin
            inc(offset, count);
            count := -count;
            f := true;
        end;
        result := 0;
        for i := offset to offset + count - 1 do begin
            inc(result, (int(commands[i]) shr 12) and $0f);
        end;
        if f then begin
            result := -result;
        end;
    end;

    function localOffsetToString(offset: int): AnsiString;
    begin
        if offset >= 0 then begin
            result := '+' + toHexString(offset, 1);
        end else begin
            result := '-' + toHexString(-offset, 1);
        end;
    end;

    function globalOffsetToString(offset: int): AnsiString;
    begin
        result := toHexString(offset, 8);
    end;
{%endregion}

{%region ClassNotFoundException }
    constructor ClassNotFoundException.create();
    begin
        inherited create();
    end;

    constructor ClassNotFoundException.create(const message: AnsiString);
    begin
        inherited create(message);
    end;
{%endregion}

{%region ClassMemberNotFoundException }
    constructor ClassMemberNotFoundException.create();
    begin
        inherited create();
    end;

    constructor ClassMemberNotFoundException.create(const message: AnsiString);
    begin
        inherited create(message);
    end;
{%endregion}

{%region NativeMethodNotFoundException }
    constructor NativeMethodNotFoundException.create();
    begin
        inherited create();
    end;

    constructor NativeMethodNotFoundException.create(const message: AnsiString);
    begin
        inherited create(message);
    end;
{%endregion}

{%region ClassFileInvalidFormatException }
    constructor ClassFileInvalidFormatException.create();
    begin
        inherited create();
    end;

    constructor ClassFileInvalidFormatException.create(const message: AnsiString);
    begin
        inherited create(message);
    end;
{%endregion}

{%region UnsupportedClassVersionException }
    constructor UnsupportedClassVersionException.create();
    begin
        inherited create();
    end;

    constructor UnsupportedClassVersionException.create(const message: AnsiString);
    begin
        inherited create(message);
    end;
{%endregion}

{%region UnsupportedBytecodeException }
    constructor UnsupportedBytecodeException.create();
    begin
        inherited create();
    end;

    constructor UnsupportedBytecodeException.create(const message: AnsiString);
    begin
        inherited create(message);
    end;
{%endregion}

{%region ManifestNotFoundException }
    constructor ManifestNotFoundException.create();
    begin
        inherited create();
    end;

    constructor ManifestNotFoundException.create(const message: AnsiString);
    begin
        inherited create(message);
    end;
{%endregion}

{%region ManifestPropertyNotFoundException }
    constructor ManifestPropertyNotFoundException.create();
    begin
        inherited create();
    end;

    constructor ManifestPropertyNotFoundException.create(const message: AnsiString);
    begin
        inherited create(message);
    end;
{%endregion}

{%region ConstantPoolEntry }
    constructor ConstantPoolEntry.create();
    begin
        inherited create();
    end;
{%endregion}

{%region StringEntry }
    constructor StringEntry.create(const value: AnsiString);
    begin
        inherited create();
        self.value := value;
    end;

    function StringEntry.stringValue(): AnsiString;
    begin
        result := value;
    end;
{%endregion}

{%region IntegerEntry }
    constructor IntegerEntry.create(value: int);
    begin
        inherited create();
        self.value := value;
    end;

    function IntegerEntry.intValue(): int;
    begin
        result := value;
    end;
{%endregion}

{%region LongEntry }
    constructor LongEntry.create(value: long);
    begin
        inherited create();
        self.value := value;
    end;

    function LongEntry.longValue(): long;
    begin
        result := value;
    end;
{%endregion}

{%region FloatEntry }
    constructor FloatEntry.create(value: float);
    begin
        inherited create();
        self.value := value;
    end;

    function FloatEntry.floatValue(): float;
    begin
        result := value;
    end;
{%endregion}

{%region DoubleEntry }
    constructor DoubleEntry.create(value: double);
    begin
        inherited create();
        self.value := value;
    end;

    function DoubleEntry.doubleValue(): double;
    begin
        result := value;
    end;
{%endregion}

{%region SingleIndexedEntry }
    constructor SingleIndexedEntry.create(entryType, index1: int);
    begin
        inherited create();
        self.entryType := entryType;
        self.index1 := index1;
    end;

    function SingleIndexedEntry.getEntryType(): int;
    begin
        result := entryType;
    end;

    function SingleIndexedEntry.getIndex1(): int;
    begin
        result := index1;
    end;
{%endregion}

{%region DoubleIndexedEntry }
    constructor DoubleIndexedEntry.create(entryType, index1, index2: int);
    begin
        inherited create(entryType, index1);
        self.index2 := index2;
    end;

    function DoubleIndexedEntry.getIndex2(): int;
    begin
        result := index2;
    end;
{%endregion}

{%region ExceptionCatch }
    constructor ExceptionCatch.create(catchOffset, classIndex: int);
    begin
        inherited create();
        self.catchOffset := catchOffset;
        self.classIndex := classIndex;
    end;

    function ExceptionCatch.getCommandIndex(): int;
    begin
        result := commandIndex;
    end;

    procedure ExceptionCatch.setCommandIndex(commandIndex: int);
    begin
        self.commandIndex := commandIndex;
    end;

    function ExceptionCatch.getCatchOffset(): int;
    begin
        result := catchOffset;
    end;

    function ExceptionCatch.getClassIndex(): int;
    begin
        result := classIndex;
    end;
{%endregion}

{%region ExceptionHandler }
    constructor ExceptionHandler.create(tryStart, tryFinish: int);
    begin
        inherited create();
        self.tryStart := tryStart;
        self.tryFinish := tryFinish;
    end;

    destructor ExceptionHandler.destroy;
    var
        c: ExceptionCatch_Array1d;
        i: int;
    begin
        c := catches;
        for i := catchesLength - 1 downto 0 do begin
            c[i].free();
        end;
        inherited destroy;
    end;

    function ExceptionHandler.getTryStart(): int;
    begin
        result := tryStart;
    end;

    function ExceptionHandler.getTryFinish(): int;
    begin
        result := tryFinish;
    end;

    function ExceptionHandler.getCatchesLength(): int;
    begin
        result := catchesLength;
    end;

    function ExceptionHandler.getCatch(index: int): ExceptionCatch;
    begin
        if (index < 0) or (index >= catchesLength) then begin
            raise ArrayIndexOutOfBoundsException.create(index);
        end;
        result := catches[index];
    end;

    procedure ExceptionHandler.addCatch(catchOffset, classIndex: int);
    var
        c: ExceptionCatch_Array1d;
        i: int;
    begin
        c := catches;
        i := catchesLength;
        if i = length(c) then begin
            c := ExceptionCatch_Array1d_create(i * 2 + 1);
            arraycopy(catches, 0, c, 0, i);
            catches := c;
        end;
        c[i] := ExceptionCatch.create(catchOffset, classIndex);
        catchesLength := i + 1;
    end;
{%endregion}

{%region InterfaceMethodImplements }
    constructor InterfaceMethodImplements.create(interfaceMethod: JavaMethod);
    begin
        inherited create();
        self.interfaceMethod := interfaceMethod;
        self.classMethod := nil;
    end;

    procedure InterfaceMethodImplements.setClassMethod(classMethod: JavaMethod);
    begin
        self.classMethod := classMethod;
    end;

    function InterfaceMethodImplements.getMethodName(): AnsiString;
    begin
        result := interfaceMethod.getName();
    end;

    function InterfaceMethodImplements.getMethodSignature(): AnsiString;
    begin
        result := interfaceMethod.getSignature();
    end;

    function InterfaceMethodImplements.getInterfaceMethod(): JavaMethod;
    begin
        result := interfaceMethod;
    end;

    function InterfaceMethodImplements.getClassMethod(): JavaMethod;
    begin
        result := classMethod;
    end;
{%endregion}

{%region InterfaceImplementsInfo }
    constructor InterfaceImplementsInfo.create(const interfaceName: AnsiString);
    begin
        inherited create();
        self.name := interfaceName;
    end;

    constructor InterfaceImplementsInfo.create(intf: JavaClass);
    begin
        inherited create();
        self.name := intf.getName();
        setInterface(intf);
    end;

    destructor InterfaceImplementsInfo.destroy;
    var
        i: int;
        a: InterfaceMethodImplements_Array1d;
    begin
        a := methods;
        for i := length(a) - 1 downto 0 do begin
            a[i].free();
            a[i] := nil;
        end;
        inherited destroy;
    end;

    procedure InterfaceImplementsInfo.setInterface(intf: JavaClass);
    var
        a: InterfaceMethodImplements_Array1d;
        c: int;
        i: int;
        j: int;
        m: JavaMethod;
    begin
        self.intf := intf;
        a := InterfaceMethodImplements_Array1d_create(intf.getAbstractMethodsCount());
        c := intf.getMethodsCount();
        j := 0;
        for i := 0 to c - 1 do begin
            m := intf.getMethod(i);
            if m.isAbstract() then begin
                a[j] := InterfaceMethodImplements.create(m);
                inc(j);
            end;
        end;
        methods := a;
    end;

    procedure InterfaceImplementsInfo.setIMTOffset(imtOffset: int);
    begin
        self.imtOffset := imtOffset;
    end;

    function InterfaceImplementsInfo.getInterfaceName(): AnsiString;
    begin
        result := name;
    end;

    function InterfaceImplementsInfo.getInterface(): JavaClass;
    begin
        result := intf;
    end;

    function InterfaceImplementsInfo.getIMTOffset(): int;
    begin
        result := imtOffset;
    end;

    function InterfaceImplementsInfo.getMethodsCount(): int;
    begin
        result := length(methods);
    end;

    function InterfaceImplementsInfo.getMethodImplements(index: int): InterfaceMethodImplements;
    var
        a: InterfaceMethodImplements_Array1d;
    begin
        a := methods;
        if (index < 0) or (index >= length(a)) then begin
            raise ArrayIndexOutOfBoundsException.create(index);
        end;
        result := a[index];
    end;
{%endregion}

{%region JavaAttribute }
    class procedure JavaAttribute.readAttributes(container: AttributeContainer; stream: DataInput; const constantPool: ConstantPoolEntry_Array1d);
    var
        i: int;
        j: int;
        name: AnsiString;
        content: byte_Array1d;
        attributeClass: JavaAttribute_Class;
    begin
        for i := stream.readUnsignedShort() downto 1 do begin
            j := stream.readUnsignedShort();
            if (j >= length(constantPool)) or (not (constantPool[j] is StringEntry)) then begin
                raise ClassFileInvalidFormatException.create('Неправильный формат class-файла: нельзя определить имя атрибута.');
            end;
            name := StringEntry(constantPool[j]).stringValue();
            j := stream.readInt();
            attributeClass := container.getAttributeClass(name);
            if attributeClass = nil then begin
                stream.skipBytes(j);
                continue;
            end;
            content := byte_Array1d_create(j);
            stream.readFully(content);
            container.onAddAttribute(attributeClass.create(container, name, content, constantPool));
        end;
    end;

    constructor JavaAttribute.create(owner: AttributeContainer; const name: AnsiString; const content: byte_Array1d; const constantPool: ConstantPoolEntry_Array1d);
    begin
        inherited create();
        self.owner := owner;
        self.content := content;
        self.name := name;
    end;

    function JavaAttribute.byteAt(offset: int): int;
    var
        c: byte_Array1d;
    begin
        c := content;
        if (offset < 0) or (offset >= length(c)) then begin
            raise ArrayIndexOutOfBoundsException.create(offset);
        end;
        result := c[offset];
    end;

    function JavaAttribute.shortAt(offset: int): int;
    var
        c: byte_Array1d;
        b1: int;
        b2: int;
    begin
        c := content;
        if (offset < 0) or (offset >= length(c) - 1) then begin
            raise ArrayIndexOutOfBoundsException.create(offset);
        end;
        b1 := c[offset];
        b2 := c[offset + 1] and $ff;
        result := (b1 shl 8) or b2;
    end;

    function JavaAttribute.intAt(offset: int): int;
    var
        c: byte_Array1d;
        b1: int;
        b2: int;
        b3: int;
        b4: int;
    begin
        c := content;
        if (offset < 0) or (offset >= length(c) - 3) then begin
            raise ArrayIndexOutOfBoundsException.create(offset);
        end;
        b1 := c[offset];
        b2 := c[offset + 1] and $ff;
        b3 := c[offset + 2] and $ff;
        b4 := c[offset + 3] and $ff;
        result := (b1 shl 24) or (b2 shl 16) or (b3 shl 8) or b4;
    end;

    function JavaAttribute.unsignedByteAt(offset: int): int;
    var
        c: byte_Array1d;
    begin
        c := content;
        if (offset < 0) or (offset >= length(c)) then begin
            raise ArrayIndexOutOfBoundsException.create(offset);
        end;
        result := c[offset] and $ff;
    end;

    function JavaAttribute.unsignedShortAt(offset: int): int;
    var
        c: byte_Array1d;
        b1: int;
        b2: int;
    begin
        c := content;
        if (offset < 0) or (offset >= length(c) - 1) then begin
            raise ArrayIndexOutOfBoundsException.create(offset);
        end;
        b1 := c[offset] and $ff;
        b2 := c[offset + 1] and $ff;
        result := (b1 shl 8) or b2;
    end;

    function JavaAttribute.getOwner(): AttributeContainer;
    begin
        result := owner;
    end;

    function JavaAttribute.getSize(): int;
    begin
        result := length(content);
    end;

    function JavaAttribute.getName(): AnsiString;
    begin
        result := name;
    end;
{%endregion}

{%region JavaConstantValue }
    constructor JavaConstantValue.create(owner: AttributeContainer; const name: AnsiString; const content: byte_Array1d; const constantPool: ConstantPoolEntry_Array1d);
    begin
        inherited create(owner, name, content, constantPool);
        case int(length(content)) of
         1: begin
            constantPoolIndex := unsignedByteAt(0);
        end;
         2..MAX_INT: begin
            constantPoolIndex := unsignedShortAt(0);
        end;
        else
            constantPoolIndex := 0;
        end;
    end;

    function JavaConstantValue.getConstantPoolIndex(): int;
    begin
        result := constantPoolIndex;
    end;
{%endregion}

{%region JavaLineNumberTable }
    constructor JavaLineNumberTable.create(owner: AttributeContainer; const name: AnsiString; const content: byte_Array1d; const constantPool: ConstantPoolEntry_Array1d);
    begin
        inherited create(owner, name, content, constantPool);
        linesCount := (length(content) - 2) div 4;
    end;

    function JavaLineNumberTable.getLinesCount(): int;
    begin
        result := linesCount;
    end;

    function JavaLineNumberTable.getLineNumber(lineIndex: int): int;
    begin
        if (lineIndex < 0) or (lineIndex >= linesCount) then begin
            raise IndexOutOfBoundsException.create(lineIndex);
        end;
        result := unsignedShortAt(lineIndex * 4 + 4);
    end;

    function JavaLineNumberTable.getCodeOffsetAt(lineIndex: int): int;
    begin
        if (lineIndex < 0) or (lineIndex >= linesCount) then begin
            raise IndexOutOfBoundsException.create(lineIndex);
        end;
        result := unsignedShortAt(lineIndex * 4 + 2);
    end;
{%endregion}

{%region JavaCode }
    class function JavaCode.extractCode(const content: byte_Array1d): byte_Array1d;
    var
        len: int;
        b1: int;
        b2: int;
        b3: int;
        b4: int;
    begin
        if length(content) < 8 then begin
            result := nil;
            exit;
        end;
        b1 := content[4];
        b2 := content[5] and $ff;
        b3 := content[6] and $ff;
        b4 := content[7] and $ff;
        len := (b1 shl 24) or (b2 shl 16) or (b3 shl 8) or b4;
        if len < 0 then begin
            result := nil;
            exit;
        end;
        result := byte_Array1d_create(len);
        arraycopy(content, 8, result, 0, len);
    end;

    constructor JavaCode.create(owner: AttributeContainer; const name: AnsiString; const content: byte_Array1d; const constantPool: ConstantPoolEntry_Array1d);
    var
        i: int;
        tryStart: int;
        tryFinish: int;
        catchOffset: int;
        classIndex: int;
        stream: DataInput;
    begin
        inherited create(owner, name, extractCode(content), constantPool);
        stream := DataInputStream.create(ByteArrayInputStream.create(content));
        stream.skipBytes(2);
        registersCount := stream.readUnsignedShort();
        stream.skipBytes(stream.readInt());
        for i := stream.readUnsignedShort() downto 1 do begin
            tryStart := stream.readUnsignedShort();
            tryFinish := stream.readUnsignedShort();
            catchOffset := stream.readUnsignedShort();
            classIndex := stream.readUnsignedShort();
            addExceptionHandler(tryStart, tryFinish, catchOffset, classIndex);
        end;
        JavaAttribute.readAttributes(self, stream, constantPool);
        sortHandlers();
        fillInstructionOffsets();
        fillTrueRegistersIndices();
    end;

    destructor JavaCode.destroy;
    var
        i: int;
        e: ExceptionHandler_Array1d;
    begin
        e := exceptionHandlers;
        for i := exceptionHandlersCount - 1 downto 0 do begin
            e[i].free();
        end;
        lineNumberTable.free();
        inherited destroy;
    end;

    procedure JavaCode.addExceptionHandler(tryStart, tryFinish, catchOffset, classIndex: int);
    var
        c: int;
        i: int;
        e: ExceptionHandler_Array1d;
        h: ExceptionHandler;
    begin
        c := exceptionHandlersCount;
        e := exceptionHandlers;
        for i := c - 1 downto 0 do begin
            h := e[i];
            if (h.getTryStart() = tryStart) and (h.getTryFinish() = tryFinish) then begin
                h.addCatch(catchOffset, classIndex);
                exit;
            end;
        end;
        if c = length(e) then begin
            e := ExceptionHandler_Array1d_create(c * 2 + 1);
            arraycopy(exceptionHandlers, 0, e, 0, c);
            exceptionHandlers := e;
        end;
        h := ExceptionHandler.create(tryStart, tryFinish);
        h.addCatch(catchOffset, classIndex);
        e[c] := h;
        exceptionHandlersCount := c + 1;
    end;

    procedure JavaCode.sortHandlers();
    var
        s1: int;
        s2: int;
        i: int;
        j: int;
        c: int;
        e: ExceptionHandler_Array1d;
        h1: ExceptionHandler;
        h2: ExceptionHandler;
    begin
        c := exceptionHandlersCount;
        e := exceptionHandlers;
        for i := 0 to c - 2 do begin
            h1 := e[i];
            for j := i + 1 to c - 1 do begin
                h2 := e[j];
                s1 := h1.getTryStart();
                s2 := h2.getTryStart();
                if (s2 < s1) or (s2 = s1) and (h1.getTryFinish() < h2.getTryFinish()) then begin
                    e[j] := h1;
                    e[i] := h2;
                    h1 := h2;
                end;
            end;
        end;
    end;

    procedure JavaCode.fillInstructionOffsets();
    var
        i1: int;
        i2: int;
        pc: int;
        size: int;
        count: int;
        buffer: int_Array1d;
        offsets: int_Array1d;
    begin
        pc := 0;
        size := getSize();
        count := 0;
        offsets := int_Array1d_create(1);
        while pc < size do begin
            if count = length(offsets) then begin
                buffer := int_Array1d_create(count * 2);
                arraycopy(offsets, 0, buffer, 0, count);
                offsets := buffer;
            end;
            offsets[count] := pc;
            inc(count);
            case unsignedByteAt(pc) of
            $00..$0f, $1a..$35, $3b..$83, $85..$98, $ac..$b1, $be..$bf, $c2..$c3: begin
                inc(pc);
            end;
            $10, $12, $15..$19, $36..$3a, $a9, $bc: begin
                inc(pc, 2);
            end;
            $11, $13..$14, $84, $99..$a8, $b2..$b8, $bb, $bd, $c0..$c1, $c6..$c7: begin
                inc(pc, 3);
            end;
            $c5: begin
                inc(pc, 4);
            end;
            $b9, $c8..$c9: begin
                inc(pc, 5);
            end;
            $c4: begin
                inc(pc);
                case unsignedByteAt(pc) of
                $15..$19, $36..$3a, $a9: begin
                    inc(pc, 3);
                end;
                $84: begin
                    inc(pc, 5);
                end;
                end;
            end;
            $aa: begin
                repeat
                    inc(pc);
                until (pc and 3) = 0;
                inc(pc, 4);
                i1 := intAt(pc);
                inc(pc, 4);
                i2 := intAt(pc);
                inc(pc, (i2 - i1 + 2) * 4);
            end;
            $ab: begin
                repeat
                    inc(pc);
                until (pc and 3) = 0;
                inc(pc, 4);
                i1 := intAt(pc);
                inc(pc, (i1 * 8) + 4);
            end;
            else
                inc(pc);
            end;
        end;
        if count < length(offsets) then begin
            instructionOffsets := int_Array1d_create(count);
            arraycopy(offsets, 0, instructionOffsets, 0, count);
            exit;
        end;
        instructionOffsets := offsets;
    end;

    procedure JavaCode.fillTrueRegistersIndices();
    var
        l: int;
        i: int;
        j: int;
        k: int;
        r: int;
        a: int_Array1d;
        m: JavaMethod;
        s: AnsiString;
    begin
        r := registersCount;
        a := int_Array1d_create(r);
        m := getOwner().asObject() as JavaMethod;
        s := m.getSignature();
        l := length(s);
        i := 0;
        j := 0;
        trueRegistersIndices := a;
        if (l > 0) and (s[1] = HEADER_PREFIX) then begin
            if not m.isStatic() then begin
                if r > 0 then begin
                    a[0] := 0;
                end;
                i := 1;
                j := 1;
            end;
            k := 2;
            while (k <= l) and (i < r) do begin
                case s[k] of
                PREFIX_DOUBLE, PREFIX_LONG: begin
                    if i + 1 < r then begin
                        a[i] := j;
                        a[i + 1] := j;
                        inc(i, 2);
                        inc(j);
                    end;
                end;
                PREFIX_OBJECT: begin
                    repeat
                        inc(k);
                    until (k > l) or (s[k] = SUFFIX_OBJECT);
                    a[i] := j;
                    inc(i);
                    inc(j);
                end;
                PREFIX_ARRAY: begin
                    repeat
                        inc(k);
                    until (k > l) or (s[k] <> PREFIX_ARRAY);
                    if k > l then begin
                        break;
                    end;
                    if s[k] = PREFIX_OBJECT then begin
                        repeat
                            inc(k);
                        until (k > l) or (s[k] = SUFFIX_OBJECT);
                    end;
                    a[i] := j;
                    inc(i);
                    inc(j);
                end;
                HEADER_SUFFIX: begin
                    break;
                end;
                else
                    a[i] := j;
                    inc(i);
                    inc(j);
                end;
                inc(k);
            end;
        end;
        while i < r do begin
            case getRegisterSize(i) of
             0: begin
                inc(i);
            end;
             1: begin
                a[i] := j;
                inc(i);
                inc(j);
            end;
             2: begin
                if getRegisterSize(i + 1) > 0 then begin
                    a[i] := j;
                    inc(i);
                    inc(j);
                end else begin
                    if i + 1 < r then begin
                        a[i] := j;
                        a[i + 1] := j;
                        inc(i, 2);
                        inc(j);
                    end;
                end;
            end;
            end;
        end;
        if (l > 0) and (s[1] = HEADER_PREFIX) then begin
            if not m.isStatic() then begin
                i := 1;
            end else begin
                i := 0;
            end;
            k := 2;
            while (k <= l) and (i < r) do begin
                case s[k] of
                PREFIX_DOUBLE, PREFIX_LONG: begin
                    inc(i);
                    if (getRegisterSize(i) > 0) and (i < r) then begin
                        a[i] := j;
                        inc(j);
                    end;
                    inc(i);
                end;
                PREFIX_OBJECT: begin
                    repeat
                        inc(k);
                    until (k > l) or (s[k] = SUFFIX_OBJECT);
                    inc(i);
                end;
                PREFIX_ARRAY: begin
                    repeat
                        inc(k);
                    until (k > l) or (s[k] <> PREFIX_ARRAY);
                    if k > l then begin
                        break;
                    end;
                    if s[k] = PREFIX_OBJECT then begin
                        repeat
                            inc(k);
                        until (k > l) or (s[k] = SUFFIX_OBJECT);
                    end;
                    inc(i);
                end;
                HEADER_SUFFIX: begin
                    break;
                end;
                else
                    inc(i);
                end;
                inc(k);
            end;
        end;
        variablesCount := j;
    end;

    function JavaCode.getRegisterSize(registerIndex: int): int;
    var
        i: int;
        pc: int;
        opcode: int;
        a: int_Array1d;
    begin
        a := instructionOffsets;
        for i := 0 to length(a) - 1 do begin
            pc := a[i];
            opcode := unsignedByteAt(pc);
            case opcode of
            $15, $17, $19, $36, $38, $3a, $84, $a9: begin
                if unsignedByteAt(pc + 1) = registerIndex then begin
                    result := 1;
                    exit;
                end;
            end;
            $c4: begin
                case unsignedByteAt(pc + 1) of
                $15, $17, $19, $36, $38, $3a, $84, $a9: begin
                    if unsignedShortAt(pc + 2) = registerIndex then begin
                        result := 1;
                        exit;
                    end;
                end;
                end;
            end;
            else
                if
                    (registerIndex >= 0) and (registerIndex < 4) and
                    (
                        (opcode - $1a = registerIndex) or (opcode - $22 = registerIndex) or (opcode - $2a = registerIndex) or
                        (opcode - $3b = registerIndex) or (opcode - $43 = registerIndex) or (opcode - $4b = registerIndex)
                    )
                then begin
                    result := 1;
                    exit;
                end;
            end;
        end;
        for i := 0 to length(a) - 1 do begin
            pc := a[i];
            opcode := unsignedByteAt(pc);
            case opcode of
            $16, $18, $37, $39: begin
                if unsignedByteAt(pc + 1) = registerIndex then begin
                    result := 2;
                    exit;
                end;
            end;
            $c4: begin
                case unsignedByteAt(pc + 1) of
                $16, $18, $37, $39: begin
                    if unsignedShortAt(pc + 2) = registerIndex then begin
                        result := 2;
                        exit;
                    end;
                end;
                end;
            end;
            else
                if
                    (registerIndex >= 0) and (registerIndex < 4) and
                    (
                        (opcode - $1e = registerIndex) or (opcode - $26 = registerIndex) or (opcode - $3f = registerIndex) or (opcode - $47 = registerIndex)
                    )
                then begin
                    result := 2;
                    exit;
                end;
            end;
        end;
        result := 0;
    end;

    procedure JavaCode.onAddAttribute(attribute: JavaAttribute);
    begin
        if (lineNumberTable = nil) and (attribute is JavaLineNumberTable) then begin
            lineNumberTable := JavaLineNumberTable(attribute);
        end;
    end;

    function JavaCode.getAttributeClass(const name: AnsiString): JavaAttribute_Class;
    begin
        if (lineNumberTable = nil) and (name = 'LineNumberTable') then begin
            result := JavaLineNumberTable;
        end else begin
            result := nil;
        end;
    end;

    function JavaCode.getInstructionsCount(): int;
    begin
        result := length(instructionOffsets);
    end;

    function JavaCode.getInstructionOffset(instructionIndex: int): int;
    var
        a: int_Array1d;
    begin
        a := instructionOffsets;
        if (instructionIndex < 0) or (instructionIndex >= length(a)) then begin
            raise ArrayIndexOutOfBoundsException.create(instructionIndex);
        end;
        result := a[instructionIndex];
    end;

    function JavaCode.getTrueRegisterIndex(javaRegisterIndex: int): int;
    var
        a: int_Array1d;
    begin
        a := trueRegistersIndices;
        if (javaRegisterIndex < 0) or (javaRegisterIndex >= length(a)) then begin
            raise ArrayIndexOutOfBoundsException.create(javaRegisterIndex);
        end;
        result := a[javaRegisterIndex];
    end;

    function JavaCode.getExceptionHandlersCount(): int;
    begin
        result := exceptionHandlersCount;
    end;

    function JavaCode.getExceptionHandler(index: int): ExceptionHandler;
    begin
        if (index < 0) or (index >= exceptionHandlersCount) then begin
            raise ArrayIndexOutOfBoundsException.create(index);
        end;
        result := exceptionHandlers[index];
    end;

    function JavaCode.getVariablesCount(): int;
    begin
        result := variablesCount;
    end;

    function JavaCode.getLineNumberTable(): JavaLineNumberTable;
    begin
        result := lineNumberTable;
    end;

    function JavaCode.indexOfInstruction(instructionOffset: int): int;
    var
        l: int;
        r: int;
        i: int;
        c: int;
        a: int_Array1d;
    begin
        a := instructionOffsets;
        l := 0;
        r := length(a) - 1;
        while l <= r do begin
            i := (l + r) shr 1;
            c := instructionOffset - a[i];
            if c > 0 then begin
                l := i + 1;
                continue;
            end;
            if c < 0 then begin
                r := i - 1;
                continue;
            end;
            result := i;
            exit;
        end;
        result := -1;
    end;
{%endregion}

{%region JavaClassMember }
    constructor JavaClassMember.create(owner: JavaClass; flags: int; const name, signature: AnsiString);
    begin
        inherited create();
        self.owner := owner;
        self.offset := 0;
        self.flags := flags;
        self.name := name;
        self.signature := signature;
    end;

    procedure JavaClassMember.setOffset(offset: int);
    begin
        self.offset := offset;
    end;

    function JavaClassMember.getCompiler(): JavaStaticRecompiler;
    begin
        result := owner.getCompiler();
    end;

    function JavaClassMember.getOwner(): JavaClass;
    begin
        result := owner;
    end;

    function JavaClassMember.getOffset(): int;
    begin
        result := offset;
    end;

    function JavaClassMember.getFlags(): int;
    begin
        result := flags;
    end;

    function JavaClassMember.getName(): AnsiString;
    begin
        result := name;
    end;

    function JavaClassMember.getSignature(): AnsiString;
    begin
        result := signature;
    end;

    function JavaClassMember.getAccess(): int;
    var
        f: int;
    begin
        f := flags;
        if (f and FLAG_PRIVATE) <> 0 then begin
            result := ACCESS_PRIVATE;
            exit;
        end;
        if (f and FLAG_PROTECTED) <> 0 then begin
            result := ACCESS_PROTECTED;
            exit;
        end;
        if (f and FLAG_PUBLIC) <> 0 then begin
            result := ACCESS_PUBLIC;
            exit;
        end;
        result := ACCESS_PACKAGED;
    end;

    function JavaClassMember.isStatic(): boolean;
    begin
        result := (flags and FLAG_STATIC) <> 0;
    end;

    function JavaClassMember.isFinal(): boolean;
    begin
        result := (flags and FLAG_FINAL) <> 0;
    end;
{%endregion}

{%region JavaField }
    constructor JavaField.create(owner: JavaClass; flags: int; const name, signature: AnsiString);
    begin
        inherited create(owner, flags, name, signature);
        constantValue := nil;
        outToExecutable := true;
    end;

    destructor JavaField.destroy;
    begin
        constantValue.free();
        inherited destroy;
    end;

    procedure JavaField.onAddAttribute(attribute: JavaAttribute);
    begin
        if (constantValue = nil) and (attribute is JavaConstantValue) then begin
            constantValue := JavaConstantValue(attribute);
            if isStatic() and isFinal() then begin
                outToExecutable := false;
            end;
        end;
    end;

    function JavaField.getAttributeClass(const name: AnsiString): JavaAttribute_Class;
    begin
        if (constantValue = nil) and (name = 'ConstantValue') then begin
            result := JavaConstantValue;
        end else begin
            result := nil;
        end;
    end;

    function JavaField.getConstantPoolIndex(): int;
    var
        c: JavaConstantValue;
    begin
        c := constantValue;
        if c <> nil then begin
            result := c.getConstantPoolIndex();
        end else begin
            result := 0;
        end;
    end;

    function JavaField.isOutToExecutable(): boolean;
    begin
        result := outToExecutable;
    end;
{%endregion}

{%region JavaMethod }
    constructor JavaMethod.create(owner: JavaClass; flags: int; const name, signature: AnsiString);
    begin
        inherited create(owner, flags, name, signature);
        parametersCount := computeParametersCount((flags and FLAG_STATIC) <> 0, signature);
    end;

    destructor JavaMethod.destroy;
    begin
        sourceCode.free();
        inherited destroy;
    end;

    procedure JavaMethod.generateSyscall();
    begin
        { public static native long syscall(long argument, int number); }
        addCommand($9004, getOwner().getLongIndex($01bf0000f9000443)) { load long $01bf0000f9000443 };
        addCommand($3042, $0002) { load int [esp + $20] [number] };
        addCommand($500b, $ffff) { load int $0000ffff };
        addCommand($1062) { and int };
        addCommand($1080) { cast int to long };
        addCommand($2079, $20) { sal long $20 };
        addCommand($1061) { or long };
        addCommand($10fe) { currthrd int };
        addCommand($300a, $0100) { load short as int $0100 };
        addCommand($1060) { or int };
        addCommand($1080) { cast int to long };
        addCommand($30f9, $0007) { syscall $0007 };
        addCommand($1084) { cast long to int };
        addCommand($2009, $10) { load byte as int $10 };
        addCommand($1050) { add int };
        addCommand($10b9) { call int };
        addCommand($20bf, $02) { ret value $02 };
    end;

    procedure JavaMethod.generateGetVariableAddress();
    begin
        { public static native int getLocalVariableAddress(<type> var); }
        { public static native int getObjectFieldAddress(<type> fld); }
        addCommand($2009, $00) { load byte as int $00 };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateVoidInterrupt();
    begin
        { static native void interrupt(int number) throws Throwable; }
        addCommand($9004, getOwner().getLongIndex($00000001bd0050ff)) { load long $00000001bd0050ff };
        addCommand($3042, $0002) { load int [esp + $20] [number] };
        addCommand($300a, $00ff) { load short as int $00ff };
        addCommand($1062) { and int };
        addCommand($1080) { cast int to long };
        addCommand($2079, $10) { sal long $10 };
        addCommand($1061) { or long };
        addCommand($10fe) { currthrd int };
        addCommand($300a, $0100) { load short as int $0100 };
        addCommand($1060) { or int };
        addCommand($1080) { cast int to long };
        addCommand($30f9, $0007) { syscall $0007 };
        addCommand($1084) { cast long to int };
        addCommand($2009, $10) { load byte as int $10 };
        addCommand($1050) { add int };
        addCommand($10b9) { call int };
        addCommand($20bd, $01) { ret $01 };
    end;

    procedure JavaMethod.generateIntInterrupt();
    begin
        { static native void interrupt(int parameter, int number) throws Throwable; }
        addCommand($9004, getOwner().getLongIndex($01bd0051ff000442)) { load long $01bd0051ff000442 };
        addCommand($3042, $0002) { load int [esp + $20] [number] };
        addCommand($300a, $00ff) { load short as int $00ff };
        addCommand($1062) { and int };
        addCommand($1080) { cast int to long };
        addCommand($2079, $28) { sal long $28 };
        addCommand($1061) { or long };
        addCommand($10fe) { currthrd int };
        addCommand($300a, $0100) { load short as int $0100 };
        addCommand($1060) { or int };
        addCommand($1080) { cast int to long };
        addCommand($30f9, $0007) { syscall $0007 };
        addCommand($1084) { cast long to int };
        addCommand($2009, $10) { load byte as int $10 };
        addCommand($1050) { add int };
        addCommand($10b9) { call int };
        addCommand($20bd, $02) { ret $02 };
    end;

    procedure JavaMethod.generateLongInterrupt();
    begin
        { static native void interrupt(long parameter, int number) throws Throwable; }
        addCommand($9004, getOwner().getLongIndex($01bd0052ff000443)) { load long $01bd0052ff000443 };
        addCommand($3042, $0002) { load int [esp + $20] [number] };
        addCommand($300a, $00ff) { load short as int $00ff };
        addCommand($1062) { and int };
        addCommand($1080) { cast int to long };
        addCommand($2079, $28) { sal long $28 };
        addCommand($1061) { or long };
        addCommand($10fe) { currthrd int };
        addCommand($300a, $0100) { load short as int $0100 };
        addCommand($1060) { or int };
        addCommand($1080) { cast int to long };
        addCommand($30f9, $0007) { syscall $0007 };
        addCommand($1084) { cast long to int };
        addCommand($2009, $10) { load byte as int $10 };
        addCommand($1050) { add int };
        addCommand($10b9) { call int };
        addCommand($20bd, $02) { ret $02 };
    end;

    procedure JavaMethod.generateObjectInterrupt();
    begin
        { static native void interrupt(Object parameter, int number) throws Throwable; }
        addCommand($9004, getOwner().getLongIndex($0001bd00fa000447)) { load long $0001bd00fa000447 };
        addCommand($3042, $0002) { load int [esp + $20] [number] };
        addCommand($300a, $00ff) { load short as int $00ff };
        addCommand($1062) { and int };
        addCommand($1080) { cast int to long };
        addCommand($2079, $20) { sal long $20 };
        addCommand($1061) { or long };
        addCommand($10fe) { currthrd int };
        addCommand($300a, $0100) { load short as int $0100 };
        addCommand($1060) { or int };
        addCommand($1080) { cast int to long };
        addCommand($30f9, $0007) { syscall $0007 };
        addCommand($1084) { cast long to int };
        addCommand($2009, $10) { load byte as int $10 };
        addCommand($1050) { add int };
        addCommand($10b9) { call int };
        addCommand($20bd, $02) { ret $02 };
    end;

    procedure JavaMethod.generateRunExcept();
    begin
        { static native void runExcept(Throwable throwable) throws Throwable; }
        addCommand($3047, $0001) { load [esp + $10] [exception] };
        addCommand($1012) { dup };
        addCommand($40c7, $0c) { getfield object +$0c [Throwable.monitor: Object] };
        addCommand($5f0b) { call Object.monitorenter() };
        addCommand($2157) { runexcept object };
    end;

    procedure JavaMethod.generateGetFloatAt();
    begin
        { static native float getFloatAt(int address); }
        addCommand($3042, $0001) { load int [esp + $10] [address] };
        addCommand($40cd, $00) { getfield float as real +$00 };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateGetDoubleAt();
    begin
        { static native double getDoubleAt(int address); }
        addCommand($3042, $0001) { load int [esp + $10] [address] };
        addCommand($40ce, $00) { getfield double as real +$00 };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateGetByteAt();
    begin
        { static native byte getByteAt(int address); }
        addCommand($3042, $0001) { load int [esp + $10] [address] };
        addCommand($40c8, $00) { getfield byte as int +$00 };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateGetShortAt();
    begin
        { static native short getShortAt(int address); }
        addCommand($3042, $0001) { load int [esp + $10] [address] };
        addCommand($40c9, $00) { getfield short as int +$00 };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateGetIntAt();
    begin
        { static native int getIntAt(int address); }
        addCommand($3042, $0001) { load int [esp + $10] [address] };
        addCommand($40ca, $00) { getfield int +$00 };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateGetLongAt();
    begin
        { static native long getLongAt(int address); }
        addCommand($3042, $0001) { load int [esp + $10] [address] };
        addCommand($40c3, $00) { getfield long +$00 };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateGetObjectAt();
    begin
        { static native Object getObjectAt(int address); }
        addCommand($3042, $0001) { load int [esp + $10] [address] };
        addCommand($40c7, $00) { getfield object +$00 };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateSetFloatAt();
    begin
        { static native void setFloatAt(int address, float value); }
        addCommand($3042, $0002) { load int [esp + $20] [address] };
        addCommand($3046, $0002) { load real [esp + $20] [value] };
        addCommand($40dd, $00) { setfield real as float +$00 };
        addCommand($20bd, $02) { ret $02 };
    end;

    procedure JavaMethod.generateSetDoubleAt();
    begin
        { static native void setDoubleAt(int address, double value); }
        addCommand($3042, $0002) { load int [esp + $20] [address] };
        addCommand($3046, $0002) { load real [esp + $20] [value] };
        addCommand($40de, $00) { setfield real as double +$00 };
        addCommand($20bd, $02) { ret $02 };
    end;

    procedure JavaMethod.generateSetByteAt();
    begin
        { static native void setByteAt(int address, byte value); }
        addCommand($3042, $0002) { load int [esp + $20] [address] };
        addCommand($3042, $0002) { load int [esp + $20] [value] };
        addCommand($40d8, $00) { setfield int as byte +$00 };
        addCommand($20bd, $02) { ret $02 };
    end;

    procedure JavaMethod.generateSetShortAt();
    begin
        { static native void setShortAt(int address, short value); }
        addCommand($3042, $0002) { load int [esp + $20] [address] };
        addCommand($3042, $0002) { load int [esp + $20] [value] };
        addCommand($40d9, $00) { setfield int as short +$00 };
        addCommand($20bd, $02) { ret $02 };
    end;

    procedure JavaMethod.generateSetIntAt();
    begin
        { static native void setIntAt(int address, int value); }
        addCommand($3042, $0002) { load int [esp + $20] [address] };
        addCommand($3042, $0002) { load int [esp + $20] [value] };
        addCommand($40da, $00) { setfield int +$00 };
        addCommand($20bd, $02) { ret $02 };
    end;

    procedure JavaMethod.generateSetLongAt();
    begin
        { static native void setLongAt(int address, long value); }
        addCommand($3042, $0002) { load int [esp + $20] [address] };
        addCommand($3043, $0002) { load long [esp + $20] [value] };
        addCommand($40d3, $00) { setfield long +$00 };
        addCommand($20bd, $02) { ret $02 };
    end;

    procedure JavaMethod.generateSetObjectAt();
    begin
        { static native void setObjectAt(int address, Object value); }
        addCommand($3042, $0002) { load int [esp + $20] [address] };
        addCommand($3047, $0002) { load object [esp + $20] [value] };
        addCommand($40d7, $00) { setfield object +$00 };
        addCommand($20bd, $02) { ret $02 };
    end;

    procedure JavaMethod.generateGetCurrentThreadID();
    begin
        { static native int getCurrentThreadID(); }
        addCommand($10fe) { currthrd int };
        addCommand($10be) { ret value };
    end;

    procedure JavaMethod.generateGetReturnAddress();
    begin
        { static native int getReturnAddress(); }
        addCommand($2009, $00) { load byte as int $00 };
        addCommand($10be) { ret value };
    end;

    procedure JavaMethod.generateConvertToReference();
    begin
        { static native int convertToReference(Object obj); }
        addCommand($3047, $0001) { load object [esp + $10] [obj] };
        addCommand($109b) { cast object to int };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateConvertToObject();
    begin
        { static native Object convertToObject(int ref); }
        addCommand($3042, $0001) { load int [esp + $10] [ref] };
        addCommand($1097) { cast int to object };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateGetMethodAddress();
    begin
        { static native int getMethodAddress(AnsiString internalRepresentation); }
        addCommand($2009, $00) { load byte as int $00 };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateGetClassInstance();
    begin
        { static native Class getClassInstance(AnsiString internalRepresentation); }
        addCommand($102f) { load null };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateArrayCopy(direction: boolean; dataType: int);
    var
        opcodeOffset: int;
    begin
        { static native void arraycopy<direction>_<dataType>(Object src, int sidx, Object dst, int didx, int length); }
        if direction then begin
            opcodeOffset := 8;
        end else begin
            opcodeOffset := 0;
        end;
        if dataType = 6 then begin
            dataType := 7;
        end;
        inc(opcodeOffset, dataType);
        addCommand($3047, $0005) { load object [esp + $50] [src] };
        addCommand($3042, $0005) { load int [esp + $50] [sidx] };
        addCommand($3047, $0005) { load object [esp + $50] [dst] };
        addCommand($3042, $0005) { load int [esp + $50] [didx] };
        addCommand($3042, $0005) { load int [esp + $50] [length] };
        addCommand($3200 + opcodeOffset) { arraycopy };
        addCommand($20bd, $05) { ret $05 };
    end;

    procedure JavaMethod.generateArrayFind(direction: boolean; dataType: int);
    var
        opcodeOffset: int;
    begin
        { static native int arrayfind<direction>_<dataType>(Object src, int sidx, <dataType> value); }
        if direction then begin
            opcodeOffset := 8;
        end else begin
            opcodeOffset := 0;
        end;
        if dataType = 6 then begin
            dataType := 7;
        end;
        inc(opcodeOffset, dataType);
        addCommand($3047, $0003) { load object [esp + $30] [src] };
        addCommand($3042, $0003) { load int [esp + $30] [sidx] };
        if (dataType >= 0) and (dataType < 2) then begin
            addCommand($3042, $0003) { load int [esp + $30] [value] };
        end else begin
            addCommand($3040 + dataType, $0003) { load <dataType> [esp + $30] [value] };
        end;
        addCommand($3210 + opcodeOffset) { arrayfind };
        addCommand($20bf, $03) { ret value $03 };
    end;

    procedure JavaMethod.generateArrayFill(dataType: int);
    begin
        { static native void arrayfill_<dataType>(Object dst, int didx, int count, <dataType> value); }
        if dataType = 4 then begin
            dataType := 7;
        end;
        addCommand($3047, $0004) { load object [esp + $40] [dst] };
        addCommand($3042, $0004) { load int [esp + $40] [didx] };
        addCommand($3042, $0004) { load int [esp + $40] [count] };
        addCommand($3040, $0004) { load [esp + $40] [value] };
        addCommand($3220 + dataType) { arrayfill };
        addCommand($20bd, $04) { ret $04 };
    end;

    procedure JavaMethod.generateFind(direction, zero: boolean);
    var
        opcodeOffset: int;
    begin
        { static native int find<zero><direction>(long[] descriptors, int index, int <zero ? refCountFieldOffset : size>); }
        opcodeOffset := 0;
        if direction then begin
            inc(opcodeOffset);
        end;
        if zero then begin
            inc(opcodeOffset, 2);
        end;
        addCommand($3047, $0003) { load object [esp + $30] [descriptors] };
        addCommand($3042, $0003) { load int [esp + $30] [index] };
        addCommand($3042, $0003) { load int [esp + $30] [<zero ? offset : size>] };
        addCommand($3228 + opcodeOffset) { find };
        addCommand($20bf, $03) { ret value $03 };
    end;

    procedure JavaMethod.generateGetObjectRefs();
    begin
        { static native int getObjectRefs(Object refThis, int refAnot, int[] refOffsets); }
        addCommand($3047, $0003) { load object [esp + $30] [refThis] };
        addCommand($3042, $0003) { load int [esp + $30] [refAnot] };
        addCommand($3047, $0003) { load object [esp + $30] [refOffsets] };
        addCommand($322c) { getobjectrefs };
        addCommand($20bf, $03) { ret value $03 };
    end;

    procedure JavaMethod.generateGetArrayRefs();
    begin
        { static native int getArrayRefs(Object refThis, int refAnot, int[] refOffsets); }
        addCommand($3047, $0003) { load object [esp + $30] [refThis] };
        addCommand($3042, $0003) { load int [esp + $30] [refAnot] };
        addCommand($3047, $0003) { load object [esp + $30] [refOffsets] };
        addCommand($322d) { getarrayrefs };
        addCommand($20bf, $03) { ret value $03 };
    end;

    procedure JavaMethod.generateBlockFindF();
    begin
        { static native int blockfindf(long[] descriptors, int startFromIndex, int block); }
        addCommand($3047, $0003) { load object [esp + $30] [descriptors] };
        addCommand($3042, $0003) { load int [esp + $30] [startFromIndex] };
        addCommand($3042, $0003) { load int [esp + $30] [block] };
        addCommand($322e) { blockfindf };
        addCommand($20bf, $03) { ret value $03 };
    end;

    procedure JavaMethod.generateBlockFindB();
    begin
        { static native int blockfindb(long[] descriptors, int startFromIndex, int block); }
        addCommand($3047, $0003) { load object [esp + $30] [descriptors] };
        addCommand($3042, $0003) { load int [esp + $30] [startFromIndex] };
        addCommand($3042, $0003) { load int [esp + $30] [block] };
        addCommand($322f) { blockfindb };
        addCommand($20bf, $03) { ret value $03 };
    end;

    procedure JavaMethod.generateInvokeDefaultConstructor();
    var
        index: int;
    begin
        { static native void invokeDefaultConstructor(Object uninitializedInstance); }
        addCommand($3047, $0001) { load object [esp + $10] [uninitializedInstance] };
        addCommand($1012) { dup };
        addCommand($10cf) { getclass };
        addCommand($40c7, $18) { getfield object Class.virtualsAddresses: int[] };
        index := (getCompiler().methodDefaultConstructor as JavaMethod).getVirtualIndex();
        if index < $80 then begin
            addCommand($2009, index) { load byte as int index };
        end else
        if index < $8000 then begin
            addCommand($300a, index) { load short as int index };
        end else begin
            addCommand($500b, index) { load int index };
        end;
        addCommand($210a) { getarraycell int };
        addCommand($10b9) { call int };
        addCommand($20bd, $01) { ret $01 };
    end;

    procedure JavaMethod.generateIntPart();
    begin
        { public static native double intPart(double x); }
        addCommand($3045, $0001) { load double [esp + $10] [x] };
        addCommand($2170) { int real };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateFracPart();
    begin
        { public static native double fracPart(double x); }
        addCommand($3045, $0001) { load double [esp + $10] [x] };
        addCommand($2171) { frac real };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateSqrt();
    begin
        { public static native double sqrt(double x); }
        addCommand($3045, $0001) { load double [esp + $10] [x] };
        addCommand($2172) { sqrt real };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateArctan();
    begin
        { public static native double arctan(double y, double x); }
        addCommand($3045, $0002) { load double [esp + $20] [y] };
        addCommand($3045, $0002) { load double [esp + $20] [x] };
        addCommand($2173) { atan real };
        addCommand($20bf, $02) { ret value $02 };
    end;

    procedure JavaMethod.generateSin();
    begin
        { public static native double sin(double x); }
        addCommand($3045, $0001) { load double [esp + $10] [x] };
        addCommand($2174) { sin real };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateCos();
    begin
        { public static native double cos(double x); }
        addCommand($3045, $0001) { load double [esp + $10] [x] };
        addCommand($2175) { cos real };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generatePow2();
    begin
        { public static native double pow2(double x); }
        addCommand($3045, $0001) { load double [esp + $10] [x] };
        addCommand($2176) { pow2 real };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateLog2();
    begin
        { public static native double log2(double x); }
        addCommand($3045, $0001) { load double [esp + $10] [x] };
        addCommand($2177) { log2 real };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateFloor();
    begin
        { public static native double floor(double x); }
        addCommand($3045, $0001) { load double [esp + $10] [x] };
        addCommand($217c) { floor real };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateCeil();
    begin
        { public static native double ceil(double x); }
        addCommand($3045, $0001) { load double [esp + $10] [x] };
        addCommand($217d) { ceil real };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.generateRound();
    begin
        { public static native long round(double x); }
        addCommand($3045, $0001) { load double [esp + $10] [x] };
        addCommand($217f) { round real };
        addCommand($20bf, $01) { ret value $01 };
    end;

    procedure JavaMethod.defineVirtualStatus();
    var
        i: int;
        access: int;
        minAccess: int;
        methodAccess: int;
        name: AnsiString;
        signature: AnsiString;
        packageName: AnsiString;
        cls: JavaClass;
        clso: JavaClass;
        owner: JavaClass;
        method: JavaMethod;
        compiler: JavaStaticRecompiler;
    begin
        if virtualStatus <> VIRTUAL_STATUS_NOT_DEFINED then begin
            exit;
        end;
        virtualStatus := VIRTUAL_STATUS_NOT_VIRTUAL;
        access := getAccess();
        owner := getOwner();
        if (isStatic()) or (access = ACCESS_PRIVATE) or (owner.isInterface()) then begin
            exit;
        end;
        compiler := getCompiler();
        name := getName();
        signature := getSignature();
        if (owner.getAccess() = ACCESS_PUBLIC) and (access = ACCESS_PUBLIC) and (name = NAME_CONSTRUCTOR) and (signature = SIGNATURE_NO_PARAMETERS) then begin
            virtualStatus := VIRTUAL_STATUS_HAS_VIRTUAL;
            cls := compiler.classObject;
            if cls <> owner then begin
                overridden := cls.findMember(NAME_CONSTRUCTOR, SIGNATURE_NO_PARAMETERS) as JavaMethod;
            end;
            exit;
        end;
        if name = NAME_CONSTRUCTOR then begin
            exit;
        end;
        if isAbstract() then begin
            virtualStatus := VIRTUAL_STATUS_HAS_VIRTUAL;
        end;
        {
            Когда вы перекрываете виртуальный метод в языке Ява, компилятор обязательно требует,
            чтобы видимость перекрывающего метода была такой же, или выше, чем у перекрываемого.
            Закомментированные участки кода метода JavaMethod.defineVirtualStatus() проверяли то
            же самое, однако это требование, как оказалось, НЕ соблюдают среды исполнения программ,
            написанных на языке Ява, и эти участки кода пришлось взять под скобки комментариев.
        }
        cls := owner.getAncestor();
        if cls <> nil then begin
            method := cls.findMember(name, signature) as JavaMethod;
            if (method <> nil) and (not method.isStatic()) and (not method.isFinal()) then begin
                packageName := owner.getPackageName();
                methodAccess := method.getAccess();
                minAccess := ACCESS_PACKAGED;
                clso := method.getOwner();
                cls := owner;
                repeat
                    cls := cls.getAncestor();
                    if packageName <> cls.getPackageName() then begin
                        minAccess := ACCESS_PROTECTED;
                        break;
                    end;
                until cls = clso;
                if (methodAccess >= minAccess) { and (access >= methodAccess) } then begin
                    virtualStatus := VIRTUAL_STATUS_HAS_VIRTUAL;
                    overridden := method;
                    exit;
                end;
            end;
        end;
        if (self.isFinal()) or (owner.isFinal()) then begin
            exit;
        end;
        for i := compiler.getClassesCount() - 1 downto 0 do begin
            cls := compiler.getClass(i);
            if (cls = owner) or (not cls.isInheritedFrom(owner)) then begin
                continue;
            end;
            method := cls.findMember(name, signature) as JavaMethod;
            if (method <> self) and (method <> nil) and (not method.isStatic()) { and (method.getAccess() >= access) } then begin
                virtualStatus := VIRTUAL_STATUS_HAS_VIRTUAL;
                exit;
            end;
        end;
    end;

    procedure JavaMethod.addCommandFull(cmd: long);
    var
        count: int;
        commands: long_Array1d;
    begin
        count := self.commandsCount;
        commands := self.commands;
        if count = length(commands) then begin
            commands := long_Array1d_create(count * 2 + 1);
            arraycopy(self.commands, 0, commands, 0, count);
            self.commands := commands;
        end;
        commands[count] := cmd;
        self.commandsCount := count + 1;
    end;

    procedure JavaMethod.addCommand();
    begin
        addCommandFull(long(0));
    end;

    procedure JavaMethod.addCommand(opcode: int);
    begin
        addCommandFull(long(opcode and $ffff));
    end;

    procedure JavaMethod.addCommand(opcode, data: int);
    begin
        addCommandFull(long(opcode and $ffff) + (long(data) shl 16));
    end;

    procedure JavaMethod.addCommand(opcode, classIndex, nameIndex, signatureIndex: int);
    begin
        addCommandFull(long(opcode and $ffff) + ((long(classIndex) and $ffff) shl 16) + ((long(nameIndex) and $ffff) shl 32) + ((long(signatureIndex) and $ffff) shl 48));
    end;

    procedure JavaMethod.transformParameters(const signature: AnsiString);
    var
        i: int;
        p: int;
        len: int;
    begin
        len := length(signature);
        p := computeParametersCount(true, signature) - 1;
        if (len > 0) and (signature[1] = HEADER_PREFIX) then begin
            i := 2;
            while i <= len do begin
                case signature[i] of
                HEADER_SUFFIX: begin
                    break;
                end;
                PREFIX_FLOAT: begin
                    addCommand($3044, p) { load float [esp + (p << 4)] };
                    addCommand($108b) { cast float to real };
                    addCommand($21f7) { mkreal float };
                    addCommand($304e, p) { store real [esp + (p << 4)] };
                    dec(p);
                end;
                PREFIX_DOUBLE: begin
                    addCommand($3045, p) { load double [esp + (p << 4)] };
                    addCommand($108f) { cast double to real };
                    addCommand($21fb) { mkreal double };
                    addCommand($304e, p) { store real [esp + (p << 4)] };
                    dec(p);
                end;
                PREFIX_BOOLEAN, PREFIX_CHAR, PREFIX_BYTE, PREFIX_SHORT, PREFIX_INT, PREFIX_LONG: begin
                    dec(p);
                end;
                PREFIX_OBJECT: begin
                    repeat
                        inc(i);
                    until (i >= len) or (signature[i] = SUFFIX_OBJECT);
                    dec(p);
                end;
                PREFIX_ARRAY: begin
                    repeat
                        inc(i);
                    until (i >= len) or (signature[i] <> PREFIX_ARRAY);
                    if i > len then begin
                        break;
                    end;
                    if signature[i] = PREFIX_OBJECT then begin
                        repeat
                            inc(i);
                        until (i >= len) or (signature[i] = SUFFIX_OBJECT);
                    end;
                    dec(p);
                end;
                end;
                inc(i);
            end;
        end;
    end;

    procedure JavaMethod.transformReturnValue(const signature: AnsiString);
    var
        p: int;
    begin
        p := pos(HEADER_SUFFIX, signature) + 1;
        if p > 1 then begin
            case signature[p] of
            PREFIX_FLOAT: begin
                addCommand($1092) { cast real to float };
            end;
            PREFIX_DOUBLE: begin
                addCommand($1093) { cast real to double };
            end;
            end;
        end;
    end;

    procedure JavaMethod.insertNativeCode(nativeIndex: int);
    begin
        insertNativeCode(nativeIndex, 0);
    end;

    procedure JavaMethod.insertNativeCode(nativeIndex, parameter: int);
    var
        index: int;
        intValue1: int;
        intValue2: int;
        owner: JavaClass;
        member: JavaClassMember;
        signature: AnsiString;
    begin
        owner := getOwner();
        case nativeIndex of
         0: begin
            addCommand($30f9, parameter) { syscall parameter };
        end;
         1.. 6: begin
            addCommand($10fe) { currthrd int };
            addCommand($300a, $0200) { load short as int $0200 };
            addCommand($1060) { or int };
            addCommand($1080) { cast int to long };
            addCommand($30f9, $0007) { syscall $0007 };
            addCommand($1084) { cast long to int };
            parameter := parameter shl 4;
            if (parameter >= -$80) and (parameter < $80) then begin
                addCommand($2009, parameter) { load byte as int parameter };
            end else
            if (parameter >= -$8000) and (parameter < $8000) then begin
                addCommand($300a, parameter) { load short as int parameter };
            end else begin
                addCommand($500b, parameter) { load int parameter };
            end;
            addCommand($1050) { add int };
        end;
         7..12: begin
            addCommand($109b) { cast object to int };
            if (parameter >= -$80) and (parameter < $80) then begin
                addCommand($2009, parameter) { load byte as int parameter };
            end else
            if (parameter >= -$8000) and (parameter < $8000) then begin
                addCommand($300a, parameter) { load short as int parameter };
            end else begin
                addCommand($500b, parameter) { load int parameter };
            end;
            addCommand($1050) { add int };
        end;
        13..15: begin
            addCommand($3150 - 13 + nativeIndex, parameter) { interrupt <void, int, long> parameter };
        end;
        16: begin
            addCommand($20fa, parameter) { interrupt object parameter };
        end;
        17: begin
            addCommand($1012) { dup };
            addCommand($40c7, $0c) { getfield object +$0c [Throwable.monitor: Object] };
            addCommand($5f0b) { call Object.monitorenter() };
            addCommand($1014) { dup x1 };
            addCommand($1016) { swap };
            addCommand($211f) { setarraylength [Throwable.address: int] };
            addCommand($10fd) { leave value };
            addCommand($2157) { runexcept object };
        end;
        18: begin
            if isStrictfp() then begin
                addCommand($40c4) { getfield float +$00 };
            end else begin
                addCommand($40cd) { getfield float as real +$00 };
            end;
        end;
        19: begin
            if isStrictfp() then begin
                addCommand($40c5) { getfield double +$00 };
            end else begin
                addCommand($40ce) { getfield double as real +$00 };
            end;
        end;
        20: begin
            addCommand($40c8) { getfield byte as int +$00 };
        end;
        21: begin
            addCommand($40c9) { getfield short as int +$00 };
        end;
        22: begin
            addCommand($40ca) { getfield int +$00 };
        end;
        23: begin
            addCommand($40c3) { getfield long +$00 };
        end;
        24: begin
            addCommand($40c7) { getfield object +$00 };
        end;
        25: begin
            if isStrictfp() then begin
                addCommand($40d4) { setfield float +$00 };
            end else begin
                addCommand($40dd) { setfield real as float +$00 };
            end;
        end;
        26: begin
            if isStrictfp() then begin
                addCommand($40d5) { setfield double +$00 };
            end else begin
                addCommand($40de) { setfield real as double +$00 };
            end;
        end;
        27: begin
            addCommand($40d8) { setfield int as byte +$00 };
        end;
        28: begin
            addCommand($40d9) { setfield int as short +$00 };
        end;
        29: begin
            addCommand($40da) { setfield int +$00 };
        end;
        30: begin
            addCommand($40d3) { setfield long +$00 };
        end;
        31: begin
            addCommand($40d7) { setfield object +$00 };
        end;
        32: begin
            addCommand($10fe) { currthrd int };
        end;
        33: begin
            if isSynchronized() then begin
                addCommand($30e0, $0002) { load [ebp + $20] };
            end else begin
                addCommand($30e0, $0001) { load [ebp + $10] };
            end;
        end;
        34: begin
            addCommand($109b) { cast object to int };
        end;
        35: begin
            addCommand($1097) { cast int to object };
        end;
        36: begin
            signature := (owner.getConstantPoolEntry(parameter) as StringEntry).stringValue();
            intValue1 := pos(SEPARATOR_CANONICAL, signature);
            intValue2 := pos(HEADER_PREFIX, signature);
            if (intValue1 < 1) or (intValue2 < 1) then begin
                raise ClassMemberNotFoundException.create('Не удалось найти метод ' + signature + '.');
            end;
            member := getCompiler().findClassByName(copy(signature, 1, intValue1 - 1)).findMember(
                copy(signature, intValue1 + 1, intValue2 - intValue1 - 1),
                copy(signature, intValue2, length(signature) - intValue2 + 1)
            );
            if (member = nil) or (not (member is JavaMethod)) then begin
                raise ClassMemberNotFoundException.create('Не удалось найти метод ' + signature + '.');
            end;
            addCommand($5f0d, owner.getStringIndex(member.getOwner().getName()), owner.getStringIndex(member.getName()), owner.getStringIndex(member.getSignature())) { load int (адрес метода) };
        end;
        37: begin
            signature := (owner.getConstantPoolEntry(parameter) as StringEntry).stringValue();
            getCompiler().findClassBySignature(signature);
            addCommand($5f06, parameter) { load int (адрес данных класса) };
            addCommand($1097) { cast int to object };
        end;
        38: begin
            addCommand($3200) { arraycopyf byte };
        end;
        39: begin
            addCommand($3201) { arraycopyf short };
        end;
        40: begin
            addCommand($3202) { arraycopyf int };
        end;
        41: begin
            addCommand($3203) { arraycopyf long };
        end;
        42: begin
            addCommand($3204) { arraycopyf float };
        end;
        43: begin
            addCommand($3205) { arraycopyf double };
        end;
        44: begin
            addCommand($3207) { arraycopyf object };
        end;
        45: begin
            addCommand($3208) { arraycopyb byte };
        end;
        46: begin
            addCommand($3209) { arraycopyb short };
        end;
        47: begin
            addCommand($320a) { arraycopyb int };
        end;
        48: begin
            addCommand($320b) { arraycopyb long };
        end;
        49: begin
            addCommand($320c) { arraycopyb float };
        end;
        50: begin
            addCommand($320d) { arraycopyb double };
        end;
        51: begin
            addCommand($320f) { arraycopyb object };
        end;
        52: begin
            addCommand($3210) { arrayfindf byte };
        end;
        53: begin
            addCommand($3211) { arrayfindf short };
        end;
        54: begin
            addCommand($3212) { arrayfindf int };
        end;
        55: begin
            addCommand($3213) { arrayfindf long };
        end;
        56: begin
            if not isStrictfp() then begin
                addCommand($1092) { cast real to float };
            end;
            addCommand($3214) { arrayfindf float };
        end;
        57: begin
            if not isStrictfp() then begin
                addCommand($1093) { cast real to double };
            end;
            addCommand($3215) { arrayfindf double };
        end;
        58: begin
            addCommand($3217) { arrayfindf object };
        end;
        59: begin
            addCommand($3218) { arrayfindb byte };
        end;
        60: begin
            addCommand($3219) { arrayfindb short };
        end;
        61: begin
            addCommand($321a) { arrayfindb int };
        end;
        62: begin
            addCommand($321b) { arrayfindb long };
        end;
        63: begin
            if not isStrictfp() then begin
                addCommand($1092) { cast real to float };
            end;
            addCommand($321c) { arrayfindb float };
        end;
        64: begin
            if not isStrictfp() then begin
                addCommand($1093) { cast real to double };
            end;
            addCommand($321d) { arrayfindb double };
        end;
        65: begin
            addCommand($321f) { arrayfindb object };
        end;
        66: begin
            addCommand($3220) { arrayfill byte };
        end;
        67: begin
            addCommand($3221) { arrayfill short };
        end;
        68: begin
            addCommand($3222) { arrayfill int };
        end;
        69: begin
            addCommand($3223) { arrayfill long };
        end;
        70: begin
            addCommand($3227) { arrayfill object };
        end;
        71: begin
            addCommand($3228) { findfreef };
        end;
        72: begin
            addCommand($3229) { findfreeb };
        end;
        73: begin
            addCommand($322a) { findzerof };
        end;
        74: begin
            addCommand($322b) { findzerob };
        end;
        75: begin
            addCommand($322c) { getobjectrefs };
        end;
        76: begin
            addCommand($322d) { getarrayrefs };
        end;
        77: begin
            addCommand($322e) { blockfindf };
        end;
        78: begin
            addCommand($322f) { blockfindb };
        end;
        79: begin
            addCommand($1012) { dup };
            addCommand($10cf) { getclass };
            addCommand($40c7, $18) { getfield object Class.virtualsAddresses: int[] };
            index := (getCompiler().methodDefaultConstructor as JavaMethod).getVirtualIndex();
            if index < $80 then begin
                addCommand($2009, index) { load byte as int index };
            end else
            if index < $8000 then begin
                addCommand($300a, index) { load short as int index };
            end else begin
                addCommand($500b, index) { load int index };
            end;
            addCommand($210a) { getarraycell int };
            addCommand($10b9) { call int };
        end;
        80: begin
            if isStrictfp() then begin
                addCommand($2168) { int double };
            end else begin
                addCommand($2170) { int real };
            end;
        end;
        81: begin
            if isStrictfp() then begin
                addCommand($2169) { frac double };
            end else begin
                addCommand($2171) { frac real };
            end;
        end;
        82: begin
            if isStrictfp() then begin
                addCommand($216a) { sqrt double };
            end else begin
                addCommand($2172) { sqrt real };
            end;
        end;
        83: begin
            if isStrictfp() then begin
                addCommand($216b) { atan double };
            end else begin
                addCommand($2173) { atan real };
            end;
        end;
        84: begin
            if isStrictfp() then begin
                addCommand($216c) { sin double };
            end else begin
                addCommand($2174) { sin real };
            end;
        end;
        85: begin
            if isStrictfp() then begin
                addCommand($216d) { cos double };
            end else begin
                addCommand($2175) { cos real };
            end;
        end;
        86: begin
            if isStrictfp() then begin
                addCommand($216e) { pow2 double };
            end else begin
                addCommand($2176) { pow2 real };
            end;
        end;
        87: begin
            if isStrictfp() then begin
                addCommand($216f) { log2 double };
            end else begin
                addCommand($2177) { log2 real };
            end;
        end;
        88: begin
            if isStrictfp() then begin
                addCommand($217a) { floor double };
            end else begin
                addCommand($217c) { floor real };
            end;
        end;
        89: begin
            if isStrictfp() then begin
                addCommand($217b) { ceil double };
            end else begin
                addCommand($217d) { ceil real };
            end;
        end;
        90: begin
            if isStrictfp() then begin
                addCommand($217e) { round double };
            end else begin
                addCommand($217f) { round real };
            end;
        end;
        end;
    end;

    function JavaMethod.getNativeMethodIndex(pc: int): int;
    var
        i: int;
        code: JavaCode;
        member: JavaClassMember;
    begin
        code := sourceCode;
        if code.unsignedByteAt(pc) = $b8 then begin
            member := getOwner().getConstantPoolMember(code.unsignedShortAt(pc + 1), MEMBER_TYPE_STATIC_METHOD);
            for i := 0 to length(NATIVE_METHODS) - 1 do begin
                if NATIVE_METHODS[i].equals(member) then begin
                    result := i;
                    exit;
                end;
            end;
        end;
        result := -1;
    end;

    function JavaMethod.getVariableOffset(javaRegisterIndex: int): int;
    var
        index: int;
        count: int;
    begin
        index := sourceCode.getTrueRegisterIndex(javaRegisterIndex);
        count := parametersCount;
        if index < count then begin
            result := count - index + 1;
            if isSynchronized() then begin
                inc(result);
            end;
            exit;
        end;
        result := count - index - 1;
    end;

    procedure JavaMethod.setVirtualIndex(virtualIndex: int);
    begin
        self.virtualIndex := virtualIndex;
    end;

    procedure JavaMethod.prepareCompile();
    var
        wide: boolean;
        clinit: boolean;
        needClinitSynchronization: boolean;
        i: int;
        j: int;
        pc: int;
        pco: int;
        index: int;
        opcode: int;
        icount: int;
        nativeIndex: int;
        classNameIndex: int;
        loadExceptIndex: int;
        exceptionHandlingStartIndex: int;
        e: ConstantPoolEntry;
        h: ExceptionHandler;
        code: JavaCode;
        owner: JavaClass;
        member: JavaClassMember;
        compiler: JavaStaticRecompiler;
        name: AnsiString;
        signature: AnsiString;
        className: AnsiString;
        memberName: AnsiString;
        memberSignature: AnsiString;
        memberOwner: JavaClass;
        memberOwnerClinit: JavaClassMember;
        intValue1: int;
        intValue2: int;
        longValue: long;
    begin
        owner := getOwner();
        compiler := owner.getCompiler();
        if isNative() then begin
            if owner = compiler.classMalikSystem then begin
                if NATIVE_METHODS[0].equals(self) then begin
                    generateSyscall();
                    exit;
                end;
                for i := 1 to 12 do begin
                    if NATIVE_METHODS[i].equals(self) then begin
                        generateGetVariableAddress();
                        exit;
                    end;
                end;
                if NATIVE_METHODS[13].equals(self) then begin
                    generateVoidInterrupt();
                    exit;
                end;
                if NATIVE_METHODS[14].equals(self) then begin
                    generateIntInterrupt();
                    exit;
                end;
                if NATIVE_METHODS[15].equals(self) then begin
                    generateLongInterrupt();
                    exit;
                end;
                if NATIVE_METHODS[16].equals(self) then begin
                    generateObjectInterrupt();
                    exit;
                end;
                if NATIVE_METHODS[17].equals(self) then begin
                    generateRunExcept();
                    exit;
                end;
                if NATIVE_METHODS[18].equals(self) then begin
                    generateGetFloatAt();
                    exit;
                end;
                if NATIVE_METHODS[19].equals(self) then begin
                    generateGetDoubleAt();
                    exit;
                end;
                if NATIVE_METHODS[20].equals(self) then begin
                    generateGetByteAt();
                    exit;
                end;
                if NATIVE_METHODS[21].equals(self) then begin
                    generateGetShortAt();
                    exit;
                end;
                if NATIVE_METHODS[22].equals(self) then begin
                    generateGetIntAt();
                    exit;
                end;
                if NATIVE_METHODS[23].equals(self) then begin
                    generateGetLongAt();
                    exit;
                end;
                if NATIVE_METHODS[24].equals(self) then begin
                    generateGetObjectAt();
                    exit;
                end;
                if NATIVE_METHODS[25].equals(self) then begin
                    generateSetFloatAt();
                    exit;
                end;
                if NATIVE_METHODS[26].equals(self) then begin
                    generateSetDoubleAt();
                    exit;
                end;
                if NATIVE_METHODS[27].equals(self) then begin
                    generateSetByteAt();
                    exit;
                end;
                if NATIVE_METHODS[28].equals(self) then begin
                    generateSetShortAt();
                    exit;
                end;
                if NATIVE_METHODS[29].equals(self) then begin
                    generateSetIntAt();
                    exit;
                end;
                if NATIVE_METHODS[30].equals(self) then begin
                    generateSetLongAt();
                    exit;
                end;
                if NATIVE_METHODS[31].equals(self) then begin
                    generateSetObjectAt();
                    exit;
                end;
                if NATIVE_METHODS[32].equals(self) then begin
                    generateGetCurrentThreadID();
                    exit;
                end;
                if NATIVE_METHODS[33].equals(self) then begin
                    generateGetReturnAddress();
                    exit;
                end;
                if NATIVE_METHODS[34].equals(self) then begin
                    generateConvertToReference();
                    exit;
                end;
                if NATIVE_METHODS[35].equals(self) then begin
                    generateConvertToObject();
                    exit;
                end;
                if NATIVE_METHODS[36].equals(self) then begin
                    generateGetMethodAddress();
                    exit;
                end;
                if NATIVE_METHODS[37].equals(self) then begin
                    generateGetClassInstance();
                    exit;
                end;
                for i := 38 to 51 do begin
                    if NATIVE_METHODS[i].equals(self) then begin
                        generateArrayCopy(i >= 45, (i - 38) mod 7);
                        exit;
                    end;
                end;
                for i := 52 to 65 do begin
                    if NATIVE_METHODS[i].equals(self) then begin
                        generateArrayFind(i >= 59, (i - 52) mod 7);
                        exit;
                    end;
                end;
                for i := 66 to 70 do begin
                    if NATIVE_METHODS[i].equals(self) then begin
                        generateArrayFill(i - 66);
                        exit;
                    end;
                end;
                for i := 71 to 74 do begin
                    if NATIVE_METHODS[i].equals(self) then begin
                        generateFind(((i - 71) and 1) <> 0, ((i - 71) and 2) <> 0);
                        exit;
                    end;
                end;
                if NATIVE_METHODS[75].equals(self) then begin
                    generateGetObjectRefs();
                    exit;
                end;
                if NATIVE_METHODS[76].equals(self) then begin
                    generateGetArrayRefs();
                    exit;
                end;
                if NATIVE_METHODS[77].equals(self) then begin
                    generateBlockFindF();
                    exit;
                end;
                if NATIVE_METHODS[78].equals(self) then begin
                    generateBlockFindB();
                    exit;
                end;
                if NATIVE_METHODS[78].equals(self) then begin
                    generateGetArrayRefs();
                    exit;
                end;
                if NATIVE_METHODS[79].equals(self) then begin
                    generateInvokeDefaultConstructor();
                    exit;
                end;
            end;
            if owner = compiler.classMath then begin
                if NATIVE_METHODS[80].equals(self) then begin
                    generateIntPart();
                    exit;
                end;
                if NATIVE_METHODS[81].equals(self) then begin
                    generateFracPart();
                    exit;
                end;
                if NATIVE_METHODS[82].equals(self) then begin
                    generateSqrt();
                    exit;
                end;
                if NATIVE_METHODS[83].equals(self) then begin
                    generateArctan();
                    exit;
                end;
                if NATIVE_METHODS[84].equals(self) then begin
                    generateSin();
                    exit;
                end;
                if NATIVE_METHODS[85].equals(self) then begin
                    generateCos();
                    exit;
                end;
                if NATIVE_METHODS[86].equals(self) then begin
                    generatePow2();
                    exit;
                end;
                if NATIVE_METHODS[87].equals(self) then begin
                    generateLog2();
                    exit;
                end;
                if NATIVE_METHODS[88].equals(self) then begin
                    generateFloor();
                    exit;
                end;
                if NATIVE_METHODS[89].equals(self) then begin
                    generateCeil();
                    exit;
                end;
                if NATIVE_METHODS[90].equals(self) then begin
                    generateRound();
                    exit;
                end;
            end;
            raise NativeMethodNotFoundException.create('Неизвестный статическому рекомпилятору «родной» метод: ' + owner.getName() + '.' + getName() + getSignature());
        end;
        code := self.sourceCode;
        if code = nil then begin
            exit;
        end;
        name := getName();
        signature := getSignature();
        className := owner.getName();
        classNameIndex := owner.getStringIndex(className);
        clinit := isStatic() and (name = NAME_INITIALIZATION) and (signature = SIGNATURE_NO_PARAMETERS);
        needClinitSynchronization := clinit and
            (className <> CLASS_MEMORY) and (className <> CLASS_CLASS) and
            (className <> CLASS_THREAD) and (className <> CLASS_THROWABLE) and
            (className <> CLASS_STRING) and (className <> CLASS_STRING_POOL)
        ;
        icount := code.getInstructionsCount();
        commandIndices := int_Array1d_create(icount + 1);
        commandsCount := 0;
        commands := nil;
        { Генерирование промежуточного кода }
        if (isStatic() or (name = NAME_CONSTRUCTOR)) and (name <> NAME_INITIALIZATION) then begin
            member := owner.findMember(NAME_INITIALIZATION, SIGNATURE_NO_PARAMETERS);
            if (member <> nil) and (member.getOwner() = owner) then begin
                addCommand($50b6, classNameIndex, owner.getStringIndex(NAME_INITIALIZATION), owner.getStringIndex(SIGNATURE_NO_PARAMETERS)) { call static owner.<clinit>() };
            end;
        end;
        if clinit then begin
            if needClinitSynchronization then begin
                addCommand($5f03, classNameIndex) { load int (адрес класса – владельца этого метода) };
                addCommand($1097) { cast int to object };
                addCommand($1012) { dup };
                addCommand($40ca, $08) { getfield int +$08 [Class.modifiers: int] };
                addCommand($2076, $1f) { shr int $1f };
                addCommand($3124, $02) { je int 0 (+2 команды) };
                addCommand($1010) { pop };
                addCommand($10bc) { ret };
                addCommand($2001, $00) { load byte as long $00 };
                addCommand($30f9, $0004) { syscall $0004 };
                addCommand($1010) { pop };
                addCommand($1012) { dup };
                addCommand($40ca, $08) { getfield int +$08 [Class.modifiers: int] };
                addCommand($2076, $1f) { shr int $1f };
                addCommand($3124, $05) { je int 0 (+5 команд) };
                addCommand($1010) { pop };
                addCommand($2001, $00) { load byte as long $00 };
                addCommand($30f9, $0005) { syscall $0005 };
                addCommand($1010) { pop };
                addCommand($10bc) { ret };
                addCommand($1012) { dup };
                addCommand($40c7, $30) { getfield object +$30 [Class.initializingThread: Thread] };
                addCommand($1012) { dup };
                addCommand($3120, $0b) { je object null (+11 команд) };
                addCommand($50b6, owner.getStringIndex(CLASS_THREAD), owner.getStringIndex(NAME_CURRENT_THREAD),
                    owner.getStringIndex(HEADER_PREFIX + HEADER_SUFFIX + PREFIX_OBJECT + CLASS_THREAD + SUFFIX_OBJECT)
                ) { call Thread.currentThread(): Thread };
                addCommand($3128, -$0b) { je object (-11 команд) };
                addCommand($2001, $00) { load byte as long $00 };
                addCommand($30f9, $0005) { syscall $0005 };
                addCommand($1010) { pop };
                addCommand($1012) { dup };
                addCommand($40ca, $08) { getfield int +$08 [Class.modifiers: int] };
                addCommand($2076, $1f) { shr int $1f };
                addCommand($3124, -$04) { je int 0 (-4 команды) };
                addCommand($1010) { pop };
                addCommand($10bc) { ret };
                addCommand($1010) { pop };
                addCommand($1012) { dup };
                addCommand($50b6, owner.getStringIndex(CLASS_THREAD), owner.getStringIndex(NAME_CURRENT_THREAD),
                    owner.getStringIndex(HEADER_PREFIX + HEADER_SUFFIX + PREFIX_OBJECT + CLASS_THREAD + SUFFIX_OBJECT)
                ) { call Thread.currentThread(): Thread };
                addCommand($40d7, $30) { setfield object +$30 [Class.initializingThread: Thread] };
                addCommand($2001, $00) { load byte as long $00 };
                addCommand($30f9, $0005) { syscall $0005 };
                addCommand($1010) { pop };
            end else begin
                addCommand($4232, $01) { jmponce (+1 команда) };
                addCommand($10bc) { ret };
            end;
        end;
        if isStrictfp() then begin
            pc := parametersCount;
            if not isStatic() then begin
                dec(pc);
            end;
            j := length(signature);
            if (j > 0) and (signature[1] = HEADER_PREFIX) then begin
                i := 2;
                while i <= j do begin
                    case signature[i] of
                    HEADER_SUFFIX: begin
                        break;
                    end;
                    PREFIX_FLOAT: begin
                        addCommand($3046, pc) { load real [esp + (pc << 4)] };
                        addCommand($1092) { cast real to float };
                        addCommand($304c, pc) { store float [esp + (pc << 4)] };
                        dec(pc);
                    end;
                    PREFIX_DOUBLE: begin
                        addCommand($3046, pc) { load real [esp + (pc << 4)] };
                        addCommand($1093) { cast real to double };
                        addCommand($304d, pc) { store double [esp + (pc << 4)] };
                        dec(pc);
                    end;
                    PREFIX_BOOLEAN, PREFIX_CHAR, PREFIX_BYTE, PREFIX_SHORT, PREFIX_INT, PREFIX_LONG: begin
                        dec(pc);
                    end;
                    PREFIX_OBJECT: begin
                        repeat
                            inc(i);
                        until (i >= j) or (signature[i] = SUFFIX_OBJECT);
                        dec(pc);
                    end;
                    PREFIX_ARRAY: begin
                        repeat
                            inc(i);
                        until (i >= j) or (signature[i] <> PREFIX_ARRAY);
                        if i > j then begin
                            break;
                        end;
                        if signature[i] = PREFIX_OBJECT then begin
                            repeat
                                inc(i);
                            until (i >= j) or (signature[i] = SUFFIX_OBJECT);
                        end;
                        dec(pc);
                    end;
                    end;
                    inc(i);
                end;
            end;
        end;
        if isSynchronized() then begin
            if isStatic() then begin
                addCommand($5f03, classNameIndex) { загрузить ссылку на класс-владелец этого метода };
                addCommand($1097) { cast int to object };
            end else begin
                addCommand($3047, parametersCount) { load object [esp + (parametersCount << 4)] [this] };
            end;
            addCommand($1012) { dup };
            addCommand($5f0b) { call Object.monitorenter() };
        end;
        addCommand($30fb, code.getVariablesCount() - parametersCount) { enter (code.getVariablesCount() - parametersCount) };
        loadExceptIndex := commandsCount;
        addCommand() { load except <…> };
        i := 0;
        while i < icount do begin
            pc := code.getInstructionOffset(i);
            commandIndices[i] := commandsCount;
            opcode := code.unsignedByteAt(pc);
            wide := false;
            if opcode = $c4 then begin
                inc(pc);
                opcode := code.unsignedByteAt(pc);
                wide := true;
            end;
            case opcode of
            $00: { nop };
            $01: begin
                { aconst_null }
                addCommand($102f) { load null };
            end;
            $02..$08: begin
                { iconst <–1…5> }
                intValue1 := opcode - $03;
                nativeIndex := getNativeMethodIndex(pc + 1);
                if (nativeIndex = 0) or (nativeIndex >= 13) and (nativeIndex <= 16) then begin
                    insertNativeCode(nativeIndex, intValue1);
                    inc(i, 2);
                    continue;
                end;
                addCommand($2009, intValue1) { load byte as int <–1…5> };
            end;
            $09..$0a: begin
                { lconst <0L…1L> }
                addCommand($2001, opcode - $09) { load byte as long <0…1> };
            end;
            $0b..$0d: begin
                { fconst <0.f, 1.f, 2.f> }
                if isStrictfp() then begin
                    addCommand($5005, -5 - $0b + opcode) { load float <0, 1, 2> };
                end else begin
                    addCommand($500e, -5 - $0b + opcode) { load float as real <0, 1, 2> };
                end;
            end;
            $0e..$0f: begin
                { dconst <0.d, 1.d> }
                if isStrictfp() then begin
                    addCommand($9006, -2 - $0e + opcode) { load double <0, 1> };
                end else begin
                    addCommand($900f, -2 - $0e + opcode) { load double as real <0, 1> };
                end;
            end;
            $10: begin
                { bipush }
                intValue1 := code.byteAt(pc + 1);
                nativeIndex := getNativeMethodIndex(pc + 2);
                if (nativeIndex = 0) or (nativeIndex >= 13) and (nativeIndex <= 16) then begin
                    insertNativeCode(nativeIndex, intValue1);
                    inc(i, 2);
                    continue;
                end;
                addCommand($2009, intValue1) { load byte as int intValue1 };
            end;
            $11: begin
                { sipush }
                intValue1 := code.shortAt(pc + 1);
                nativeIndex := getNativeMethodIndex(pc + 3);
                if (nativeIndex = 0) or (nativeIndex >= 13) and (nativeIndex <= 16) then begin
                    insertNativeCode(nativeIndex, intValue1);
                    inc(i, 2);
                    continue;
                end;
                if (intValue1 >= -$80) and (intValue1 < $80) then begin
                    addCommand($2009, intValue1) { load byte as int intValue1 };
                end else begin
                    addCommand($300a, intValue1) { load short as int intValue1 };
                end;
            end;
            $12..$13: begin
                { ldc }
                { ldc_w }
                if opcode = $12 then begin
                    index := code.unsignedByteAt(pc + 1);
                    inc(pc, 2);
                end else begin
                    index := code.unsignedShortAt(pc + 1);
                    inc(pc, 3);
                end;
                e := owner.getConstantPoolEntry(index);
                if e is IntegerEntry then begin
                    intValue1 := IntegerEntry(e).intValue();
                    nativeIndex := getNativeMethodIndex(pc);
                    if (nativeIndex = 0) or (nativeIndex >= 13) and (nativeIndex <= 16) then begin
                        insertNativeCode(nativeIndex, intValue1);
                        inc(i, 2);
                        continue;
                    end;
                    if (intValue1 >= -$80) and (intValue1 < $80) then begin
                        addCommand($2009, intValue1) { load byte as int intValue1 };
                    end else
                    if (intValue1 >= -$8000) and (intValue1 < $8000) then begin
                        addCommand($300a, intValue1) { load short as int intValue1 };
                    end else begin
                        addCommand($500b, intValue1) { load int intValue1 };
                    end;
                end else
                if e is FloatEntry then begin
                    if isStrictfp() then begin
                        addCommand($5005, index) { load float constantPool[index] };
                    end else begin
                        addCommand($500e, index) { load float as real constantPool[index] };
                    end;
                end else
                if (e is SingleIndexedEntry) and (SingleIndexedEntry(e).getEntryType() = ENTRY_STRING) then begin
                    index := SingleIndexedEntry(e).getIndex1();
                    nativeIndex := getNativeMethodIndex(pc);
                    if (nativeIndex >= 36) and (nativeIndex <= 37) then begin
                        insertNativeCode(nativeIndex, index);
                        inc(i, 2);
                        continue;
                    end;
                    addCommand($5f00, index) { загрузить индекс строки };
                    addCommand($5f01) { call static StringPool.getString(int): AnsiString };
                end;
            end;
            $14: begin
                { ldc2_w }
                index := code.unsignedShortAt(pc + 1);
                e := owner.getConstantPoolEntry(index);
                if e is LongEntry then begin
                    longValue := LongEntry(e).longValue();
                    if (longValue >= -$80) and (longValue < $80) then begin
                        addCommand($2001, int(longValue)) { load byte as long (int) longValue };
                    end else
                    if (longValue >= -$8000) and (longValue < $8000) then begin
                        addCommand($3002, int(longValue)) { load short as long (int) longValue };
                    end else
                    if (longValue >= -$80000000) and (longValue < $80000000) then begin
                        addCommand($5003, int(longValue)) { load int as long (int) longValue };
                    end else begin
                        addCommand($9004, index) { load long constantPool[index] };
                    end;
                end else
                if e is DoubleEntry then begin
                    if isStrictfp() then begin
                        addCommand($9006, index) { load double constantPool[index] };
                    end else begin
                        addCommand($900f, index) { load double as real constantPool[index] };
                    end;
                end;
            end;
            $15, $1a..$1d: begin
                { iload }
                if opcode = $15 then begin
                    if wide then begin
                        index := code.unsignedShortAt(pc + 1);
                        inc(pc, 3);
                    end else begin
                        index := code.unsignedByteAt(pc + 1);
                        inc(pc, 2);
                    end;
                end else begin
                    index := opcode - $1a;
                    inc(pc);
                end;
                index := getVariableOffset(index);
                nativeIndex := getNativeMethodIndex(pc);
                if (nativeIndex >= 1) and (nativeIndex <= 6) then begin
                    insertNativeCode(nativeIndex, index);
                    inc(i, 2);
                    continue;
                end;
                addCommand($30e2, index) { load int [ebp + (index << 4)] };
            end;
            $16, $1e..$21: begin
                { lload }
                if opcode = $16 then begin
                    if wide then begin
                        index := code.unsignedShortAt(pc + 1);
                        inc(pc, 3);
                    end else begin
                        index := code.unsignedByteAt(pc + 1);
                        inc(pc, 2);
                    end;
                end else begin
                    index := opcode - $1e;
                    inc(pc);
                end;
                index := getVariableOffset(index);
                nativeIndex := getNativeMethodIndex(pc);
                if (nativeIndex >= 1) and (nativeIndex <= 6) then begin
                    insertNativeCode(nativeIndex, index);
                    inc(i, 2);
                    continue;
                end;
                addCommand($30e3, index) { load long [ebp + (index << 4)] };
            end;
            $17, $22..$25: begin
                { fload }
                if opcode = $17 then begin
                    if wide then begin
                        index := code.unsignedShortAt(pc + 1);
                        inc(pc, 3);
                    end else begin
                        index := code.unsignedByteAt(pc + 1);
                        inc(pc, 2);
                    end;
                end else begin
                    index := opcode - $22;
                    inc(pc);
                end;
                index := getVariableOffset(index);
                nativeIndex := getNativeMethodIndex(pc);
                if (nativeIndex >= 1) and (nativeIndex <= 6) then begin
                    insertNativeCode(nativeIndex, index);
                    inc(i, 2);
                    continue;
                end;
                addCommand($30e4, index) { load float [ebp + (index << 4)] };
            end;
            $18, $26..$29: begin
                { dload }
                if opcode = $18 then begin
                    if wide then begin
                        index := code.unsignedShortAt(pc + 1);
                        inc(pc, 3);
                    end else begin
                        index := code.unsignedByteAt(pc + 1);
                        inc(pc, 2);
                    end;
                end else begin
                    index := opcode - $26;
                    inc(pc);
                end;
                index := getVariableOffset(index);
                nativeIndex := getNativeMethodIndex(pc);
                if (nativeIndex >= 1) and (nativeIndex <= 6) then begin
                    insertNativeCode(nativeIndex, index);
                    inc(i, 2);
                    continue;
                end;
                addCommand($30e5, index) { load double [ebp + (index << 4)] };
            end;
            $19, $2a..$2d: begin
                { aload }
                if opcode = $19 then begin
                    if wide then begin
                        index := code.unsignedShortAt(pc + 1);
                        inc(pc, 3);
                    end else begin
                        index := code.unsignedByteAt(pc + 1);
                        inc(pc, 2);
                    end;
                end else begin
                    index := opcode - $2a;
                    inc(pc);
                end;
                index := getVariableOffset(index);
                nativeIndex := getNativeMethodIndex(pc);
                if (nativeIndex >= 1) and (nativeIndex <= 6) then begin
                    insertNativeCode(nativeIndex, index);
                    inc(i, 2);
                    continue;
                end;
                addCommand($30e7, index) { load object [ebp + (index << 4)] };
            end;
            $2e: begin
                { iaload }
                addCommand($210a) { getarraycell int };
            end;
            $2f: begin
                { laload }
                addCommand($2103) { getarraycell long };
            end;
            $30: begin
                { faload }
                if isStrictfp() then begin
                    addCommand($2104) { getarraycell float };
                end else begin
                    addCommand($210d) { getarraycell float as real };
                end;
            end;
            $31: begin
                { daload }
                if isStrictfp() then begin
                    addCommand($2105) { getarraycell double };
                end else begin
                    addCommand($210e) { getarraycell double as real };
                end;
            end;
            $32: begin
                { aaload }
                addCommand($2107) { getarraycell object };
            end;
            $33: begin
                { baload }
                addCommand($2108) { getarraycell byte as int };
            end;
            $34: begin
                { caload }
                addCommand($210b) { getarraycell wchar };
            end;
            $35: begin
                { saload }
                addCommand($2109) { getarraycell short as int };
            end;
            $36, $3b..$3e: begin
                { istore }
                if opcode = $36 then begin
                    if wide then begin
                        index := code.unsignedShortAt(pc + 1);
                    end else begin
                        index := code.unsignedByteAt(pc + 1);
                    end;
                end else begin
                    index := opcode - $3b;
                end;
                addCommand($30ea, getVariableOffset(index)) { store int [ebp + (index << 4)] };
            end;
            $37, $3f..$42: begin
                { lstore }
                if opcode = $37 then begin
                    if wide then begin
                        index := code.unsignedShortAt(pc + 1);
                    end else begin
                        index := code.unsignedByteAt(pc + 1);
                    end;
                end else begin
                    index := opcode - $3f;
                end;
                addCommand($30eb, getVariableOffset(index)) { store long [ebp + (index << 4)] };
            end;
            $38, $43..$46: begin
                { fstore }
                if opcode = $38 then begin
                    if wide then begin
                        index := code.unsignedShortAt(pc + 1);
                    end else begin
                        index := code.unsignedByteAt(pc + 1);
                    end;
                end else begin
                    index := opcode - $43;
                end;
                index := getVariableOffset(index);
                if isStrictfp() then begin
                    addCommand($30ec, index) { store float [ebp + (index << 4)] };
                end else begin
                    addCommand($30ee, index) { store real [ebp + (index << 4)] };
                end;
            end;
            $39, $47..$4a: begin
                { dstore }
                if opcode = $39 then begin
                    if wide then begin
                        index := code.unsignedShortAt(pc + 1);
                    end else begin
                        index := code.unsignedByteAt(pc + 1);
                    end;
                end else begin
                    index := opcode - $47;
                end;
                index := getVariableOffset(index);
                if isStrictfp() then begin
                    addCommand($30ed, index) { store double [ebp + (index << 4)] };
                end else begin
                    addCommand($30ee, index) { store real [ebp + (index << 4)] };
                end;
            end;
            $3a, $4b..$4e: begin
                { astore }
                if opcode = $3a then begin
                    if wide then begin
                        index := code.unsignedShortAt(pc + 1);
                    end else begin
                        index := code.unsignedByteAt(pc + 1);
                    end;
                end else begin
                    index := opcode - $4b;
                end;
                addCommand($30ef, getVariableOffset(index)) { store object [ebp + (index << 4)] };
            end;
            $4f: begin
                { iastore }
                addCommand($211a) { setarraycell int };
            end;
            $50: begin
                { lastore }
                addCommand($2113) { setarraycell long };
            end;
            $51: begin
                { fastore }
                if isStrictfp() then begin
                    addCommand($2114) { setarraycell float };
                end else begin
                    addCommand($211d) { setarraycell real as float };
                end;
            end;
            $52: begin
                { dastore }
                if isStrictfp() then begin
                    addCommand($2115) { setarraycell double };
                end else begin
                    addCommand($211e) { setarraycell real as double };
                end;
            end;
            $53: begin
                { aastore }
                addCommand($3047, $0002) { load object [esp + $20] };
                addCommand($1012) { dup };
                addCommand($3042, $0003) { load int [esp + $30] };
                addCommand($2156) { arraybound };
                addCommand($3047, $0001) { load object [esp + $10] };
                addCommand($5f02) { call Object.checkObjectArrayAssignable(Object) };
                addCommand($2117) { setarraycell object };
            end;
            $54: begin
                { bastore }
                addCommand($2118) { setarraycell int as byte };
            end;
            $55: begin
                { castore }
                addCommand($211b) { setarraycell wchar };
            end;
            $56: begin
                { sastore }
                addCommand($2119) { setarraycell int as short };
            end;
            $57: begin
                { pop }
                addCommand($1018) { popdw };
            end;
            $58: begin
                { pop2 }
                addCommand($1019) { popdw2 };
            end;
            $59: begin
                { dup }
                addCommand($101a) { dupdw };
            end;
            $5a: begin
                { dup_x1 }
                addCommand($101c) { dupdw x1 };
            end;
            $5b: begin
                { dup_x2 }
                addCommand($101e) { dupdw x2 };
            end;
            $5c: begin
                { dup2 }
                addCommand($101b) { dupdw2 };
            end;
            $5d: begin
                { dup2_x1 }
                addCommand($101d) { dupdw2 x1 };
            end;
            $5e: begin
                { dup2_x2 }
                addCommand($101f) { dupdw2 x2 };
            end;
            $5f: begin
                { swap }
                addCommand($101c) { dupdw x1 };
                addCommand($1018) { popdw };
            end;
            $60: begin
                { iadd }
                addCommand($1050) { add int };
            end;
            $61: begin
                { ladd }
                addCommand($1051) { add long };
            end;
            $62: begin
                { fadd }
                if isStrictfp() then begin
                    addCommand($1052) { add float };
                end else begin
                    addCommand($1070) { add real };
                end;
            end;
            $63: begin
                { dadd }
                if isStrictfp() then begin
                    addCommand($1053) { add double };
                end else begin
                    addCommand($1070) { add real };
                end;
            end;
            $64: begin
                { isub }
                addCommand($1054) { sub int };
            end;
            $65: begin
                { lsub }
                addCommand($1055) { sub long };
            end;
            $66: begin
                { fsub }
                if isStrictfp() then begin
                    addCommand($1056) { sub float };
                end else begin
                    addCommand($1071) { sub real };
                end;
            end;
            $67: begin
                { dsub }
                if isStrictfp() then begin
                    addCommand($1057) { sub double };
                end else begin
                    addCommand($1071) { sub real };
                end;
            end;
            $68: begin
                { imul }
                addCommand($1058) { mul int };
            end;
            $69: begin
                { lmul }
                addCommand($1059) { mul long };
            end;
            $6a: begin
                { fmul }
                if isStrictfp() then begin
                    addCommand($105a) { mul float };
                end else begin
                    addCommand($1072) { mul real };
                end;
            end;
            $6b: begin
                { dmul }
                if isStrictfp() then begin
                    addCommand($105b) { mul double };
                end else begin
                    addCommand($1072) { mul real };
                end;
            end;
            $6c: begin
                { idiv }
                addCommand($105c) { div int };
            end;
            $6d: begin
                { ldiv }
                addCommand($105d) { div long };
            end;
            $6e: begin
                { fdiv }
                if isStrictfp() then begin
                    addCommand($105e) { div float };
                end else begin
                    addCommand($1073) { div real };
                end;
            end;
            $6f: begin
                { ddiv }
                if isStrictfp() then begin
                    addCommand($105f) { div double };
                end else begin
                    addCommand($1073) { div real };
                end;
            end;
            $70: begin
                { irem }
                addCommand($106c) { rem int };
            end;
            $71: begin
                { lrem }
                addCommand($106d) { rem long };
            end;
            $72: begin
                { frem }
                if isStrictfp() then begin
                    addCommand($106e) { rem float };
                end else begin
                    addCommand($1077) { rem real };
                end;
            end;
            $73: begin
                { drem }
                if isStrictfp() then begin
                    addCommand($106f) { rem double };
                end else begin
                    addCommand($1077) { rem real };
                end;
            end;
            $74: begin
                { ineg }
                addCommand($107c) { neg int };
            end;
            $75: begin
                { lneg }
                addCommand($107d) { neg long };
            end;
            $76: begin
                { fneg }
                if isStrictfp() then begin
                    addCommand($107e) { neg float };
                end else begin
                    addCommand($107b) { neg real };
                end;
            end;
            $77: begin
                { dneg }
                if isStrictfp() then begin
                    addCommand($107f) { neg double };
                end else begin
                    addCommand($107b) { neg real };
                end;
            end;
            $78: begin
                { ishl }
                addCommand($1068) { sal int };
            end;
            $79: begin
                { lshl }
                addCommand($1069) { sal long };
            end;
            $7a: begin
                { ishr }
                addCommand($1066) { sar int };
            end;
            $7b: begin
                { lshr }
                addCommand($1067) { sar long };
            end;
            $7c: begin
                { iushr }
                addCommand($106a) { shr int };
            end;
            $7d: begin
                { lushr }
                addCommand($106b) { shr long };
            end;
            $7e: begin
                { iand }
                addCommand($1062) { and int };
            end;
            $7f: begin
                { land }
                addCommand($1063) { and long };
            end;
            $80: begin
                { ior }
                addCommand($1060) { or int };
            end;
            $81: begin
                { lor }
                addCommand($1061) { or long };
            end;
            $82: begin
                { ixor }
                addCommand($1064) { xor int };
            end;
            $83: begin
                { lxor }
                addCommand($1065) { xor long };
            end;
            $84: begin
                { iinc }
                if wide then begin
                    index := code.unsignedShortAt(pc + 1);
                    intValue1 := code.shortAt(pc + 3);
                end else begin
                    index := code.unsignedByteAt(pc + 1);
                    intValue1 := code.byteAt(pc + 2);
                end;
                index := getVariableOffset(index);
                if intValue1 = 1 then begin
                    addCommand($41f1, index) { inc int [ebp + (index << 4)] };
                end else
                if intValue1 = -1 then begin
                    addCommand($41f9, index) { dec int [ebp + (index << 4)] };
                end else begin
                    if (intValue1 >= -$80) and (intValue1 < $80) then begin
                        addCommand($2009, intValue1) { load byte as int intValue1 };
                    end else begin
                        addCommand($300a, intValue1) { load short as int intValue1 };
                    end;
                    addCommand($41c0, index) { add int [ebp + (index << 4)] };
                end;
            end;
            $85: begin
                { i2l }
                addCommand($1080) { cast int to long };
            end;
            $86: begin
                { i2f }
                if isStrictfp() then begin
                    addCommand($1081) { cast int to float };
                end else begin
                    addCommand($1083) { cast int to real };
                    addCommand($21f7) { mkreal float };
                end;
            end;
            $87: begin
                { i2d }
                if isStrictfp() then begin
                    addCommand($1082) { cast int to double };
                end else begin
                    addCommand($1083) { cast int to real };
                    addCommand($21fb) { mkreal double };
                end;
            end;
            $88: begin
                { l2i }
                addCommand($1084) { cast long to int };
            end;
            $89: begin
                { l2f }
                if isStrictfp() then begin
                    addCommand($1085) { cast long to float };
                end else begin
                    addCommand($1087) { cast long to real };
                    addCommand($21f7) { mkreal float };
                end;
            end;
            $8a: begin
                { l2d }
                if isStrictfp() then begin
                    addCommand($1086) { cast long to double };
                end else begin
                    addCommand($1087) { cast long to real };
                    addCommand($21fb) { mkreal double };
                end;
            end;
            $8b: begin
                { f2i }
                if isStrictfp() then begin
                    addCommand($1088) { cast float to int };
                end else begin
                    addCommand($1090) { cast real to int };
                end;
            end;
            $8c: begin
                { f2l }
                if isStrictfp() then begin
                    addCommand($1089) { cast float to long };
                end else begin
                    addCommand($1091) { cast real to long };
                end;
            end;
            $8d: begin
                { f2d }
                addCommand($108a) { cast float to double };
            end;
            $8e: begin
                { d2i }
                if isStrictfp() then begin
                    addCommand($108c) { cast double to int };
                end else begin
                    addCommand($1090) { cast real to int };
                end;
            end;
            $8f: begin
                { d2l }
                if isStrictfp() then begin
                    addCommand($108d) { cast double to long };
                end else begin
                    addCommand($1091) { cast real to long };
                end;
            end;
            $90: begin
                { d2f }
                addCommand($108e) { cast double to float };
            end;
            $91: begin
                { i2b }
                addCommand($1094) { cast int to byte };
            end;
            $92: begin
                { i2c }
                addCommand($1096) { cast int to wchar };
            end;
            $93: begin
                { i2s }
                addCommand($1095) { cast int to short };
            end;
            $94: begin
                { lcmp }
                addCommand($10f1) { cmp long };
            end;
            $95: begin
                { fcmpl }
                if isStrictfp() then begin
                    addCommand($10f2) { cmpl float };
                end else begin
                    addCommand($10f6) { cmpl real };
                end;
            end;
            $96: begin
                { fcmpg }
                if isStrictfp() then begin
                    addCommand($10f3) { cmpg float };
                end else begin
                    addCommand($10f7) { cmpg real };
                end;
            end;
            $97: begin
                { dcmpl }
                if isStrictfp() then begin
                    addCommand($10f4) { cmpl double };
                end else begin
                    addCommand($10f6) { cmpl real };
                end;
            end;
            $98: begin
                { dcmpg }
                if isStrictfp() then begin
                    addCommand($10f5) { cmpg double };
                end else begin
                    addCommand($10f7) { cmpg real };
                end;
            end;
            $99: begin
                { ifeq }
                addCommand($6144) { je int 0 <…> };
            end;
            $9a: begin
                { ifne }
                addCommand($6145) { jne int 0 <…> };
            end;
            $9b: begin
                { iflt }
                addCommand($6146) { jl int 0 <…> };
            end;
            $9c: begin
                { ifge }
                addCommand($6147) { jge int 0 <…> };
            end;
            $9d: begin
                { ifgt }
                addCommand($6142) { jg int 0 <…> };
            end;
            $9e: begin
                { ifle }
                addCommand($6143) { jle int 0 <…> };
            end;
            $9f: begin
                { if_icmpeq }
                addCommand($614c) { je int <…> };
            end;
            $a0: begin
                { if_icmpne }
                addCommand($614d) { jne int <…> };
            end;
            $a1: begin
                { if_icmplt }
                addCommand($614e) { jl int <…> };
            end;
            $a2: begin
                { if_icmpge }
                addCommand($614f) { jge int <…> };
            end;
            $a3: begin
                { if_icmpgt }
                addCommand($614a) { jg int <…> };
            end;
            $a4: begin
                { if_icmple }
                addCommand($614b) { jle int <…> };
            end;
            $a5: begin
                { if_acmpeq }
                addCommand($6148) { je object <…> };
            end;
            $a6: begin
                { if_acmpne }
                addCommand($6149) { jne object <…> };
            end;
            $a7, $c8: begin
                { goto, goto_w }
                addCommand($50b2) { jmp <…> };
            end;
            $aa: begin
                { tableswitch }
                repeat
                    inc(pc);
                until (pc and 3) = 0;
                intValue1 := code.intAt(pc + 4);
                intValue2 := code.intAt(pc + 8);
                if intValue1 > intValue2 then begin
                    raise UnsupportedBytecodeException.create('Tableswitch: минимальное значение не должно быть больше максимального.');
                end;
                addCommand($615a, intValue1) { tableswitch int (min=intValue1, };
                addCommand($4fff, intValue2) { max=intValue2, };
                addCommand($4ffe, intValue2 - intValue1 + 1, 0, 0) { default=<…>, };
                for j := intValue1 to intValue2 do begin
                    addCommand($4ffe, intValue2 - j, 0, 0) { label[j]=<…>) };
                end;
            end;
            $ab: begin
                { lookupswitch }
                repeat
                    inc(pc);
                until (pc and 3) = 0;
                intValue1 := code.intAt(pc + 4);
                if intValue1 < 0 then begin
                    raise UnsupportedBytecodeException.create('Lookupswitch: количество меток не должно быть отрицательным.');
                end;
                addCommand($415e, intValue1) { lookupswitch int (labels=intValue1, };
                addCommand($4ffe, intValue1 * 2, 0, 0) { default=<…>, };
                for j := 1 to intValue1 do begin
                    addCommand($4fff, code.intAt(pc + (j * 8))) { value[j], };
                    addCommand($4ffe, (intValue1 - j) * 2, 0, 0) { label[j]=<…>) };
                end;
            end;
            $ac..$b0: begin
                { ireturn, lreturn, freturn, dreturn, areturn }
                if isStrictfp() then begin
                    case opcode of
                    $ae: begin
                        addCommand($108b) { cast float to real };
                        addCommand($21f7) { mkreal float };
                    end;
                    $af: begin
                        addCommand($108f) { cast double to real };
                        addCommand($21fb) { mkreal double };
                    end;
                    end;
                end;
                addCommand($10fd) { leave value };
                if isSynchronized() then begin
                    addCommand($1016) { swap };
                    addCommand($5f0c) { call Object.monitorexit() };
                end;
                if parametersCount > 0 then begin
                    addCommand($20bf, parametersCount) { ret value parametersCount };
                end else begin
                    addCommand($10be) { ret value };
                end;
            end;
            $b1: begin
                { return }
                if (not isStatic()) and (name = NAME_FINALIZE) and (signature = SIGNATURE_NO_PARAMETERS) then begin
                    index := getVariableOffset(0);
                    for j := 0 to owner.getFieldsCount() - 1 do begin
                        member := owner.getField(j);
                        memberSignature := member.getSignature();
                        if (not member.isStatic()) and (length(memberSignature) > 0) and (memberSignature[1] in [PREFIX_ARRAY, PREFIX_OBJECT]) then begin
                            addCommand($30e7, index) { load object [ebp + (index << 4)] [this] };
                            addCommand($102f) { load null };
                            addCommand($40d7, member.getOffset()) { setfield object this.member: Object };
                        end;
                    end;
                    member := overridden;
                    if member <> nil then begin
                        addCommand($30e7, index) { load object [ebp + (index << 4)] [this] };
                        addCommand($50b6, owner.getStringIndex(member.getOwner().getName()), owner.getStringIndex(member.getName()), owner.getStringIndex(member.getSignature())) { call super.$finalize$() };
                    end;
                end;
                addCommand($10fc) { leave };
                if isSynchronized() then begin
                    addCommand($5f0c) { call Object.monitorexit() };
                end;
                if needClinitSynchronization then begin
                    addCommand($1012) { dup };
                    addCommand($1012) { dup };
                    addCommand($40ca, $08) { getfield int +$08 [Class.modifiers: int] };
                    addCommand($500b, $80000000) { load int $80000000 };
                    addCommand($1060) { or int };
                    addCommand($40da, $08) { setfield int +$08 [Class.modifiers: int] };
                    addCommand($102f) { load null };
                    addCommand($40d7, $30) { setfield object +$30 [Class.initializingThread: Thread] };
                end;
                if owner.isInheritedFrom(compiler.interfaceInterrupt) and (not isStatic()) and (name = NAME_INTERRUPT) and (parametersCount >= 1) and (parametersCount <= 2) then begin
                    case parametersCount of
                     1: begin
                        addCommand($10ba) { iret $01 };
                    end;
                     2: begin
                        addCommand($10bb) { iret $02 };
                    end;
                    end;
                end else begin
                    if parametersCount > 0 then begin
                        addCommand($20bd, parametersCount) { ret parametersCount };
                    end else begin
                        addCommand($10bc) { ret };
                    end;
                end;
            end;
            $b2: begin
                { getstatic }
                member := owner.getConstantPoolMember(code.unsignedShortAt(pc + 1), MEMBER_TYPE_STATIC_FIELD);
                if (not (member is JavaField)) or (not member.isStatic()) or (not JavaField(member).isOutToExecutable()) then begin
                    raise ClassMemberNotFoundException.create('Команде getstatic требуется ' + MEMBER_TYPES[MEMBER_TYPE_STATIC_FIELD] + ', не имеющее постоянного значения.');
                end;
                memberOwner := member.getOwner();
                memberName := member.getName();
                memberSignature := member.getSignature();
                if memberOwner <> owner then begin
                    memberOwnerClinit := memberOwner.findMemberInternal(NAME_INITIALIZATION, SIGNATURE_NO_PARAMETERS);
                    if memberOwnerClinit <> nil then begin
                        addCommand($50b6,
                            owner.getStringIndex(memberOwner.getName()),
                            owner.getStringIndex(NAME_INITIALIZATION),
                            owner.getStringIndex(SIGNATURE_NO_PARAMETERS)
                        ) { call static memberOwnerClinit.<clinit>() };
                    end;
                end;
                if length(memberSignature) > 0 then begin
                    case memberSignature[1] of
                    PREFIX_BOOLEAN, PREFIX_BYTE: begin
                        addCommand($5028,
                            owner.getStringIndex(memberOwner.getName()),
                            owner.getStringIndex(memberName),
                            owner.getStringIndex(memberSignature)
                        ) { load byte as int [memberOwner.member: <boolean, byte>] };
                    end;
                    PREFIX_CHAR: begin
                        addCommand($502b,
                            owner.getStringIndex(memberOwner.getName()),
                            owner.getStringIndex(memberName),
                            owner.getStringIndex(memberSignature)
                        ) { load wchar [memberOwner.member: char] };
                    end;
                    PREFIX_FLOAT: begin
                        if isStrictfp() then begin
                            addCommand($5024,
                                owner.getStringIndex(memberOwner.getName()),
                                owner.getStringIndex(memberName),
                                owner.getStringIndex(memberSignature)
                            ) { load float [memberOwner.member: float] };
                        end else begin
                            addCommand($502d,
                                owner.getStringIndex(memberOwner.getName()),
                                owner.getStringIndex(memberName),
                                owner.getStringIndex(memberSignature)
                            ) { load float as real [memberOwner.member: float] };
                        end;
                    end;
                    PREFIX_DOUBLE: begin
                        if isStrictfp() then begin
                            addCommand($5025,
                                owner.getStringIndex(memberOwner.getName()),
                                owner.getStringIndex(memberName),
                                owner.getStringIndex(memberSignature)
                            ) { load double [memberOwner.member: double] };
                        end else begin
                            addCommand($502e,
                                owner.getStringIndex(memberOwner.getName()),
                                owner.getStringIndex(memberName),
                                owner.getStringIndex(memberSignature)
                            ) { load double as real [memberOwner.member: double] };
                        end;
                    end;
                    PREFIX_SHORT: begin
                        addCommand($5029,
                            owner.getStringIndex(memberOwner.getName()),
                            owner.getStringIndex(memberName),
                            owner.getStringIndex(memberSignature)
                        ) { load short as int [memberOwner.member: short] };
                    end;
                    PREFIX_INT: begin
                        addCommand($502a,
                            owner.getStringIndex(memberOwner.getName()),
                            owner.getStringIndex(memberName),
                            owner.getStringIndex(memberSignature)
                        ) { load int [memberOwner.member: int] };
                    end;
                    PREFIX_LONG: begin
                        addCommand($5023,
                            owner.getStringIndex(memberOwner.getName()),
                            owner.getStringIndex(memberName),
                            owner.getStringIndex(memberSignature)
                        ) { load long [memberOwner.member: long] };
                    end;
                    PREFIX_OBJECT, PREFIX_ARRAY: begin
                        addCommand($5027,
                            owner.getStringIndex(memberOwner.getName()),
                            owner.getStringIndex(memberName),
                            owner.getStringIndex(memberSignature)
                        ) { load object [memberOwner.member: Object] };
                    end;
                    end;
                end;
            end;
            $b3: begin
                { putstatic }
                member := owner.getConstantPoolMember(code.unsignedShortAt(pc + 1), MEMBER_TYPE_STATIC_FIELD);
                if (not (member is JavaField)) or (not member.isStatic()) or (not JavaField(member).isOutToExecutable()) then begin
                    raise ClassMemberNotFoundException.create('Команде putstatic требуется ' + MEMBER_TYPES[MEMBER_TYPE_STATIC_FIELD] + ', не имеющее постоянного значения.');
                end;
                memberOwner := member.getOwner();
                memberName := member.getName();
                memberSignature := member.getSignature();
                if memberOwner <> owner then begin
                    memberOwnerClinit := memberOwner.findMemberInternal(NAME_INITIALIZATION, SIGNATURE_NO_PARAMETERS);
                    if memberOwnerClinit <> nil then begin
                        addCommand($50b6,
                            owner.getStringIndex(memberOwner.getName()),
                            owner.getStringIndex(NAME_INITIALIZATION),
                            owner.getStringIndex(SIGNATURE_NO_PARAMETERS)
                        ) { call static memberOwnerClinit.<clinit>() };
                    end;
                end;
                if length(memberSignature) > 0 then begin
                    case memberSignature[1] of
                    PREFIX_BOOLEAN, PREFIX_BYTE: begin
                        addCommand($5038,
                            owner.getStringIndex(memberOwner.getName()),
                            owner.getStringIndex(memberName),
                            owner.getStringIndex(memberSignature)
                        ) { store int as byte [memberOwner.member: <boolean, byte>] };
                    end;
                    PREFIX_CHAR: begin
                        addCommand($503b,
                            owner.getStringIndex(memberOwner.getName()),
                            owner.getStringIndex(memberName),
                            owner.getStringIndex(memberSignature)
                        ) { store wchar [memberOwner.member: char] };
                    end;
                    PREFIX_FLOAT: begin
                        if isStrictfp() then begin
                            addCommand($5034,
                                owner.getStringIndex(memberOwner.getName()),
                                owner.getStringIndex(memberName),
                                owner.getStringIndex(memberSignature)
                            ) { store float [memberOwner.member: float] };
                        end else begin
                            addCommand($503d,
                                owner.getStringIndex(memberOwner.getName()),
                                owner.getStringIndex(memberName),
                                owner.getStringIndex(memberSignature)
                            ) { store real as float [memberOwner.member: float] };
                        end;
                    end;
                    PREFIX_DOUBLE: begin
                        if isStrictfp() then begin
                            addCommand($5035,
                                owner.getStringIndex(memberOwner.getName()),
                                owner.getStringIndex(memberName),
                                owner.getStringIndex(memberSignature)
                            ) { store double [memberOwner.member: double] };
                        end else begin
                            addCommand($503e,
                                owner.getStringIndex(memberOwner.getName()),
                                owner.getStringIndex(memberName),
                                owner.getStringIndex(memberSignature)
                            ) { store real as double [memberOwner.member: double] };
                        end;
                    end;
                    PREFIX_SHORT: begin
                        addCommand($5039,
                            owner.getStringIndex(memberOwner.getName()),
                            owner.getStringIndex(memberName),
                            owner.getStringIndex(memberSignature)
                        ) { store int as short [memberOwner.member: short] };
                    end;
                    PREFIX_INT: begin
                        addCommand($503a,
                            owner.getStringIndex(memberOwner.getName()),
                            owner.getStringIndex(memberName),
                            owner.getStringIndex(memberSignature)
                        ) { store int [memberOwner.member: int] };
                    end;
                    PREFIX_LONG: begin
                        addCommand($5033,
                            owner.getStringIndex(memberOwner.getName()),
                            owner.getStringIndex(memberName),
                            owner.getStringIndex(memberSignature)
                        ) { store long [memberOwner.member: long] };
                    end;
                    PREFIX_OBJECT, PREFIX_ARRAY: begin
                        addCommand($5037,
                            owner.getStringIndex(memberOwner.getName()),
                            owner.getStringIndex(memberName),
                            owner.getStringIndex(memberSignature)
                        ) { store object [memberOwner.member: Object] };
                    end;
                    end;
                end;
            end;
            $b4: begin
                { getfield }
                member := owner.getConstantPoolMember(code.unsignedShortAt(pc + 1), MEMBER_TYPE_INSTANCE_FIELD);
                if (not (member is JavaField)) or member.isStatic() then begin
                    raise ClassMemberNotFoundException.create('Команде getfield требуется ' + MEMBER_TYPES[MEMBER_TYPE_INSTANCE_FIELD] + '.');
                end;
                memberSignature := member.getSignature();
                nativeIndex := getNativeMethodIndex(pc + 3);
                if (nativeIndex >= 7) and (nativeIndex <= 12) then begin
                    insertNativeCode(nativeIndex, member.getOffset());
                    inc(i, 2);
                    continue;
                end;
                if length(memberSignature) > 0 then begin
                    case memberSignature[1] of
                    PREFIX_BOOLEAN, PREFIX_BYTE: begin
                        addCommand($40c8, member.getOffset()) { getfield byte as int memberOwner.member: <boolean, byte> };
                    end;
                    PREFIX_CHAR: begin
                        addCommand($40cb, member.getOffset()) { getfield wchar memberOwner.member: char };
                    end;
                    PREFIX_FLOAT: begin
                        if isStrictfp() then begin
                            addCommand($40c4, member.getOffset()) { getfield float memberOwner.member: float };
                        end else begin
                            addCommand($40cd, member.getOffset()) { getfield float as real memberOwner.member: float };
                        end;
                    end;
                    PREFIX_DOUBLE: begin
                        if isStrictfp() then begin
                            addCommand($40c5, member.getOffset()) { getfield double memberOwner.member: double };
                        end else begin
                            addCommand($40ce, member.getOffset()) { getfield double as real memberOwner.member: double };
                        end;
                    end;
                    PREFIX_SHORT: begin
                        addCommand($40c9, member.getOffset()) { getfield short as int memberOwner.member: short };
                    end;
                    PREFIX_INT: begin
                        addCommand($40ca, member.getOffset()) { getfield int memberOwner.member: int };
                    end;
                    PREFIX_LONG: begin
                        addCommand($40c3, member.getOffset()) { getfield long memberOwner.member: long };
                    end;
                    PREFIX_OBJECT, PREFIX_ARRAY: begin
                        addCommand($40c7, member.getOffset()) { getfield object memberOwner.member: Object };
                    end;
                    end;
                end;
            end;
            $b5: begin
                { putfield }
                member := owner.getConstantPoolMember(code.unsignedShortAt(pc + 1), MEMBER_TYPE_INSTANCE_FIELD);
                if (not (member is JavaField)) or member.isStatic() then begin
                    raise ClassMemberNotFoundException.create('Команде putfield требуется ' + MEMBER_TYPES[MEMBER_TYPE_INSTANCE_FIELD] + '.');
                end;
                memberSignature := member.getSignature();
                if length(memberSignature) > 0 then begin
                    case memberSignature[1] of
                    PREFIX_BOOLEAN, PREFIX_BYTE: begin
                        addCommand($40d8, member.getOffset()) { setfield int as byte memberOwner.member: <boolean, byte> };
                    end;
                    PREFIX_CHAR: begin
                        addCommand($40db, member.getOffset()) { setfield wchar memberOwner.member: char };
                    end;
                    PREFIX_FLOAT: begin
                        if isStrictfp() then begin
                            addCommand($40d4, member.getOffset()) { setfield float memberOwner.member: float };
                        end else begin
                            addCommand($40dd, member.getOffset()) { setfield real as float memberOwner.member: float };
                        end;
                    end;
                    PREFIX_DOUBLE: begin
                        if isStrictfp() then begin
                            addCommand($40d5, member.getOffset()) { setfield double memberOwner.member: double };
                        end else begin
                            addCommand($40de, member.getOffset()) { setfield real as double memberOwner.member: double };
                        end;
                    end;
                    PREFIX_SHORT: begin
                        addCommand($40d9, member.getOffset()) { setfield int as short memberOwner.member: short };
                    end;
                    PREFIX_INT: begin
                        addCommand($40da, member.getOffset()) { setfield int memberOwner.member: int };
                    end;
                    PREFIX_LONG: begin
                        addCommand($40d3, member.getOffset()) { setfield long memberOwner.member: long };
                    end;
                    PREFIX_OBJECT, PREFIX_ARRAY: begin
                        addCommand($40d7, member.getOffset()) { setfield object memberOwner.member: Object };
                    end;
                    end;
                end;
            end;
            $b6: begin
                { invokevirtual }
                member := owner.getConstantPoolMember(code.unsignedShortAt(pc + 1), MEMBER_TYPE_INSTANCE_METHOD);
                if (not (member is JavaMethod)) or member.isStatic() then begin
                    raise ClassMemberNotFoundException.create('Команде invokevirtual требуется ' + MEMBER_TYPES[MEMBER_TYPE_INSTANCE_METHOD] + '.');
                end;
                memberSignature := member.getSignature();
                if JavaMethod(member).isVirtual() then begin
                    if isStrictfp() then begin
                        transformParameters(memberSignature);
                    end;
                    addCommand($3047, JavaMethod(member).parametersCount - 1) { load object [esp + ((JavaMethod(member).parametersCount - 1) << 4)] [this для вызываемого метода] };
                    addCommand($10cf) { getclass };
                    addCommand($40c7, $18) { getfield object Class.virtualsAddresses: int[] };
                    index := JavaMethod(member).getVirtualIndex();
                    if index < $80 then begin
                        addCommand($2009, index) { load byte as int index };
                    end else
                    if index < $8000 then begin
                        addCommand($300a, index) { load short as int index };
                    end else begin
                        addCommand($500b, index) { load int index };
                    end;
                    addCommand($210a) { getarraycell int };
                    addCommand($10b9) { call int };
                end else begin
                    addCommand($5230, JavaMethod(member).parametersCount - 1) { check object null [esp + ((JavaMethod(member).parametersCount - 1) << 4)] [this для вызываемого метода] };
                    if isStrictfp() then begin
                        transformParameters(memberSignature);
                    end;
                    addCommand($50b6, owner.getStringIndex(member.getOwner().getName()), owner.getStringIndex(member.getName()), owner.getStringIndex(memberSignature)) { call member };
                end;
                if isStrictfp() then begin
                    transformReturnValue(memberSignature);
                end;
            end;
            $b7: begin
                { invokespecial }
                member := owner.getConstantPoolMember(code.unsignedShortAt(pc + 1), MEMBER_TYPE_INSTANCE_METHOD);
                if (not (member is JavaMethod)) or member.isStatic() then begin
                    raise ClassMemberNotFoundException.create('Команде invokestatic требуется ' + MEMBER_TYPES[MEMBER_TYPE_INSTANCE_METHOD] + '.');
                end;
                memberSignature := member.getSignature();
                addCommand($5230, JavaMethod(member).parametersCount - 1) { check object null [esp + ((JavaMethod(member).parametersCount - 1) << 4)] [this для вызываемого метода] };
                if isStrictfp() then begin
                    transformParameters(memberSignature);
                end;
                addCommand($50b6, owner.getStringIndex(member.getOwner().getName()), owner.getStringIndex(member.getName()), owner.getStringIndex(memberSignature)) { call member };
                if isStrictfp() then begin
                    transformReturnValue(memberSignature);
                end;
            end;
            $b8: begin
                { invokestatic }
                member := owner.getConstantPoolMember(code.unsignedShortAt(pc + 1), MEMBER_TYPE_STATIC_METHOD);
                if (not (member is JavaMethod)) or (not member.isStatic()) then begin
                    raise ClassMemberNotFoundException.create('Команде invokestatic требуется ' + MEMBER_TYPES[MEMBER_TYPE_STATIC_METHOD] + '.');
                end;
                nativeIndex := getNativeMethodIndex(pc);
                if (nativeIndex >= 17) and (nativeIndex <= 35) or (nativeIndex >= 38) then begin
                    insertNativeCode(nativeIndex);
                    inc(i);
                    continue;
                end;
                memberSignature := member.getSignature();
                if isStrictfp() then begin
                    transformParameters(memberSignature);
                end;
                addCommand($50b6, owner.getStringIndex(member.getOwner().getName()), owner.getStringIndex(member.getName()), owner.getStringIndex(memberSignature)) { call <static method address> };
                if isStrictfp() then begin
                    transformReturnValue(memberSignature);
                end;
            end;
            $b9: begin
                { invokeinterface }
                member := owner.getConstantPoolMember(code.unsignedShortAt(pc + 1), MEMBER_TYPE_INTERFACE_METHOD);
                memberOwner := member.getOwner();
                if (not (member is JavaMethod)) or member.isStatic() or (not memberOwner.isInterface()) or (not JavaMethod(member).isAbstract()) then begin
                    raise ClassMemberNotFoundException.create('Команде invokeinterface требуется ' + MEMBER_TYPES[MEMBER_TYPE_INTERFACE_METHOD] + '.');
                end;
                memberSignature := member.getSignature();
                if isStrictfp() then begin
                    transformParameters(memberSignature);
                end;
                addCommand($3047, JavaMethod(member).parametersCount - 1) { load object [esp + ((JavaMethod(member).parametersCount - 1) << 4)] [this для вызываемого метода] };
                addCommand($10cf) { getclass };
                addCommand($5f03, owner.getStringIndex(memberOwner.getName())) { load int (адрес данных протокола) };
                addCommand($1097) { cast int to object };
                index := JavaMethod(member).getVirtualIndex() * IMT_ENTRY_SIZE;
                if index < $80 then begin
                    addCommand($2009, index) { load byte as int index };
                end else
                if index < $8000 then begin
                    addCommand($300a, index) { load short as int index };
                end else begin
                    addCommand($500b, index) { load int index };
                end;
                addCommand($5f04) { call Class.getInterfaceMethodEntryPoint(Class, int): int };
                addCommand($10b9) { call int };
                if isStrictfp() then begin
                    transformReturnValue(memberSignature);
                end;
            end;
            $bb: begin
                { new }
                index := code.unsignedShortAt(pc + 1);
                index := (owner.getConstantPoolEntry(index) as SingleIndexedEntry).getIndex1();
                className := (owner.getConstantPoolEntry(index) as StringEntry).stringValue();
                compiler.findClassByName(className);
                addCommand($5f03, index) { load int (адрес класса, инстанцию которого следует создать) };
                addCommand($1097) { cast int to object };
                addCommand($5f05) { call Class.allocateInstance(): Object };
            end;
            $bc: begin
                { newarray }
                className := '';
                case code.unsignedByteAt(pc + 1) of
                 4: className := PREFIX_ARRAY + PREFIX_BOOLEAN;
                 5: className := PREFIX_ARRAY + PREFIX_CHAR;
                 6: className := PREFIX_ARRAY + PREFIX_FLOAT;
                 7: className := PREFIX_ARRAY + PREFIX_DOUBLE;
                 8: className := PREFIX_ARRAY + PREFIX_BYTE;
                 9: className := PREFIX_ARRAY + PREFIX_SHORT;
                10: className := PREFIX_ARRAY + PREFIX_INT;
                11: className := PREFIX_ARRAY + PREFIX_LONG;
                end;
                compiler.findClassBySignature(className);
                addCommand($5f06, owner.getStringIndex(className)) { load int (адрес класса, инстанцию которого следует создать) };
                addCommand($1097) { cast int to object };
                addCommand($5f07) { call static Array.create(int, Class): Object };
            end;
            $bd: begin
                { anewarray }
                index := code.unsignedShortAt(pc + 1);
                index := (owner.getConstantPoolEntry(index) as SingleIndexedEntry).getIndex1();
                className := (owner.getConstantPoolEntry(index) as StringEntry).stringValue();
                if (length(className) > 0) and (className[1] in [PREFIX_ARRAY, PREFIX_OBJECT]) then begin
                    className := PREFIX_ARRAY + className;
                end else begin
                    className := PREFIX_ARRAY + PREFIX_OBJECT + className + SUFFIX_OBJECT;
                end;
                compiler.findClassBySignature(className);
                addCommand($5f06, owner.getStringIndex(className)) { load int (адрес класса, инстанцию которого следует создать) };
                addCommand($1097) { cast int to object };
                addCommand($5f07) { call static Array.create(int, Class): Object };
            end;
            $be: begin
                { arraylength }
                addCommand($210f) { getarraylength };
            end;
            $bf: begin
                { athrow }
                addCommand($20fa, $20) { interrupt object $20 };
            end;
            $c0..$c1: begin
                { checkcast, instanceof }
                index := code.unsignedShortAt(pc + 1);
                index := (owner.getConstantPoolEntry(index) as SingleIndexedEntry).getIndex1();
                className := (owner.getConstantPoolEntry(index) as StringEntry).stringValue();
                if (length(className) = 0) or (not (className[1] in [PREFIX_ARRAY, PREFIX_OBJECT])) then begin
                    className := PREFIX_OBJECT + className + SUFFIX_OBJECT;
                end;
                compiler.findClassBySignature(className);
                addCommand($5f06, owner.getStringIndex(className)) { load int (адрес класса для проверки) };
                addCommand($1097) { cast int to object };
                addCommand($1016) { swap };
                addCommand($5f09 - $c0 + opcode) { call Class.cast(Object): Object } { call Class.isInstance(Object): boolean };
            end;
            $c2..$c3: begin
                { monitorenter, monitorexit }
                addCommand($5230, $0000) { check object null [esp + $00] };
                addCommand($5f0b - $c2 + opcode) { call Object.monitorenter() } { call Object.monitorexit() };
            end;
            $c5: begin
                { multianewarray }
                index := code.unsignedShortAt(pc + 1);
                index := (owner.getConstantPoolEntry(index) as SingleIndexedEntry).getIndex1();
                className := (owner.getConstantPoolEntry(index) as StringEntry).stringValue();
                intValue1 := code.unsignedByteAt(pc + 3);
                compiler.findClassBySignature(PREFIX_ARRAY + PREFIX_INT);
                compiler.findClassBySignature(className);
                if intValue1 < $80 then begin
                    addCommand($2009, intValue1) { load byte as int intValue1 };
                end else begin
                    addCommand($300a, intValue1) { load short as int intValue1 };
                end;
                addCommand($5f06, owner.getStringIndex(PREFIX_ARRAY + PREFIX_INT)) { load int (адрес класса int[]) };
                addCommand($1097) { cast int to object };
                addCommand($5f07) { call static Array.create(int, Class): Object };
                for j := 0 to intValue1 - 1 do begin
                    addCommand($1012) { dup };
                    if j < $80 then begin
                        addCommand($2009, j) { load byte as int j };
                    end else begin
                        addCommand($300a, j) { load short as int j };
                    end;
                    addCommand($3042, intValue1 - j + 2) { load int [esp + ((intValue1 - j + 2) << 4)] };
                    addCommand($211a) { setarraycell int };
                end;
                addCommand($5f06, index) { load int (адрес класса создаваемого многомерного массива) };
                addCommand($1097) { cast int to object };
                addCommand($5f08) { call static Array.create(int[], Class): Object };
                addCommand($304f, intValue1 - 1) { store object [esp + ((intValue1 - 1) << 4)] };
                addCommand($3011, intValue1 - 1) { pop (intValue1 - 1) };
            end;
            $c6..$c7: begin
                { ifnull, ifnonnull }
                addCommand($6140 - $c6 + opcode) { je object null <…> } { jne object null <…> };
            end;
            $ca: begin
                { breakpoint }
                addCommand($103f) { breakpoint };
            end;
            $a8..$a9, $ba, $c9, $fe, $ff: begin
                { jsr, ret, invokedynamic, jsr_w, impdep1, impdep2 }
                raise UnsupportedBytecodeException.create('Команды jsr, ret, invokedynamic, jsr_w, impdep1, impdep2 не поддерживаются.');
            end;
            else
                raise UnsupportedBytecodeException.create('Неизвестный байт-код $' + toHexString(long(opcode), 2) + '.');
            end;
            inc(i);
        end;
        commandIndices[icount] := commandsCount;
        { Генерирование кода для обработки исключений }
        exceptionHandlingStartIndex := commandsCount;
        commands[loadExceptIndex] := long($5008) + ((long(exceptionHandlingStartIndex) - long(loadExceptIndex) - 1) shl 16);
        for i := code.getExceptionHandlersCount() - 1 downto 0 do begin
            h := code.getExceptionHandler(i);
            pc := 0;
            for j := 0 to h.getCatchesLength() - 1 do begin
                index := h.getCatch(j).getClassIndex();
                if index > 0 then begin
                    inc(pc, 5);
                end;
                inc(pc, 6);
            end;
            addCommand($1012) { dup };
            addCommand($210f) { getarraylength [Throwable.address: int] };
            addCommand($5ffd, commandIndices[code.indexOfInstruction(h.getTryStart())]) { load int try[i].start };
            addCommand($614e, pc + 4) { jl int (+(pc + 4) команды) };
            addCommand($1012) { dup };
            addCommand($210f) { getarraylength [Throwable.address: int] };
            addCommand($5ffd, commandIndices[code.indexOfInstruction(h.getTryFinish())]) { load int try[i].finish };
            addCommand($614f, pc) { jge int (+pc команд) };
            for j := 0 to h.getCatchesLength() - 1 do begin
                index := h.getCatch(j).getClassIndex();
                if index > 0 then begin
                    index := (owner.getConstantPoolEntry(index) as SingleIndexedEntry).getIndex1();
                    addCommand($5f03, index) { загрузить ссылку на класс исключения };
                    addCommand($1097) { cast int to object };
                    addCommand($3047, $0001) { load object [esp + $10] };
                    addCommand($5f0a) { call Class.isInstance(Object): boolean };
                    addCommand($6144, $06) { je int 0 (+6 команд) };
                end;
                addCommand($5008, exceptionHandlingStartIndex - commandsCount - 1) { load except exceptionHandlingStartIndex };
                addCommand($1016) { swap };
                addCommand($1012) { dup };
                addCommand($40c7, $0c) { getfield object +$0c [Throwable.monitor: Object] };
                addCommand($5f0c) { call Object.monitorexit() };
                addCommand($50b2, commandIndices[code.indexOfInstruction(h.getCatch(j).getCatchOffset())] - commandsCount - 1) { jmp try[i].catch[j] };
            end;
        end;
        if isSynchronized() then begin
            addCommand($30e7, $0001) { load object [ebp + $10] <monitor> };
            addCommand($5f0c) { call Object.monitorexit() };
            if needClinitSynchronization then begin
                addCommand($30e7, $0002) { load object [ebp + $20] };
                addCommand($1012) { dup };
                addCommand($1012) { dup };
                addCommand($40ca, $08) { getfield int +$08 [Class.modifiers: int] };
                addCommand($500b, $80000000) { load int $80000000 };
                addCommand($1060) { or int };
                addCommand($40da, $08) { setfield int +$08 [Class.modifiers: int] };
                addCommand($102f) { load null };
                addCommand($40d7, $30) { setfield object +$30 [Class.initializingThread: Thread] };
                addCommand($30e0, $0003) { load [ebp + $30] <return address> };
            end else begin
                addCommand($1012) { dup };
                addCommand($30e0, $0002) { load [ebp + $20] <return address> };
            end;
        end else begin
            if needClinitSynchronization then begin
                addCommand($30e7, $0001) { load object [ebp + $10] <monitor> };
                addCommand($1012) { dup };
                addCommand($1012) { dup };
                addCommand($40ca, $08) { getfield int +$08 [Class.modifiers: int] };
                addCommand($500b, $80000000) { load int $80000000 };
                addCommand($1060) { or int };
                addCommand($40da, $08) { setfield int +$08 [Class.modifiers: int] };
                addCommand($102f) { load null };
                addCommand($40d7, $30) { setfield object +$30 [Class.initializingThread: Thread] };
                addCommand($30e0, $0002) { load [ebp + $20] <return address> };
            end else begin
                addCommand($1012) { dup };
                addCommand($30e0, $0001) { load [ebp + $10] <return address> };
            end;
        end;
        addCommand($21fa) { dec int };
        addCommand($211f) { setarraylength [Throwable.address: int] };
        addCommand($2157) { runexcept object };
        { Настройка меток }
        for index := 0 to icount - 1 do begin
            pc := code.getInstructionOffset(index);
            opcode := code.unsignedByteAt(pc);
            case opcode of
            $99..$a7, $c6..$c8: begin
                { if, goto }
                if opcode <> $c8 then begin
                    i := pc + code.shortAt(pc + 1);
                end else begin
                    i := pc + code.intAt(pc + 1);
                end;
                i := commandIndices[code.indexOfInstruction(i)];
                j := commandIndices[index];
                commands[j] := commands[j] + ((long(i) - long(j) - 1) shl 16);
            end;
            $aa: begin
                { tableswitch }
                pco := pc;
                repeat
                    inc(pc);
                until (pc and 3) = 0;
                i := commandIndices[code.indexOfInstruction(pco + code.intAt(pc))];
                j := commandIndices[index] + 2;
                commands[j] := buildLong(int(commands[j]), i - j - 1);
                inc(j);
                intValue1 := code.intAt(pc + 4);
                intValue2 := code.intAt(pc + 8);
                inc(pc, 12);
                for intValue1 := intValue1 to intValue2 do begin
                    i := commandIndices[code.indexOfInstruction(pco + code.intAt(pc))];
                    commands[j] := buildLong(int(commands[j]), i - j - 1);
                    inc(j);
                    inc(pc, 4);
                end;
            end;
            $ab: begin
                { lookupswitch }
                pco := pc;
                repeat
                    inc(pc);
                until (pc and 3) = 0;
                i := commandIndices[code.indexOfInstruction(pco + code.intAt(pc))];
                j := commandIndices[index] + 1;
                commands[j] := buildLong(int(commands[j]), i - j - 1);
                inc(j, 2);
                intValue1 := code.intAt(pc + 4);
                inc(pc, 12);
                for intValue1 := intValue1 downto 1 do begin
                    i := commandIndices[code.indexOfInstruction(pco + code.intAt(pc))];
                    commands[j] := buildLong(int(commands[j]), i - j - 1);
                    inc(j, 2);
                    inc(pc, 8);
                end;
            end;
            end;
        end;
    end;

    procedure JavaMethod.fillStringPool();
    var
        i: int;
        index: int;
        cmd: long;
        commands: long_Array1d;
        owner: JavaClass;
        compiler: JavaStaticRecompiler;
        str: AnsiString;
    begin
        commands := self.commands;
        owner := getOwner();
        compiler := owner.getCompiler();
        for i := 0 to commandsCount - 1 do begin
            cmd := commands[i];
            if (int(cmd) and $0fff) <> $0f00 then begin
                continue;
            end;
            str := (owner.getConstantPoolEntry(int(cmd shr 16)) as StringEntry).stringValue();
            index := compiler.getStringPoolStringIndex(str);
            if index < $80 then begin
                commands[i] := long($2009) + (long(index) shl 16);
            end else
            if index < $8000 then begin
                commands[i] := long($300a) + (long(index) shl 16);
            end else begin
                commands[i] := long($500b) + (long(index) shl 16);
            end;
        end;
    end;

    procedure JavaMethod.writeCode(stream: DataOutput);
    begin
        getCompiler().writeCode(stream, commands, commandsCount, getOffset(), getOwner());
    end;

    function JavaMethod.reduceSizeofJumps(): boolean;
    begin
        result := getCompiler().reduceSizeofJumps(commands, commandsCount);
    end;

    function JavaMethod.reduceSizeofCalls(): boolean;
    begin
        result := getCompiler().reduceSizeofCalls(commands, commandsCount, getOffset(), getOwner());
    end;

    function JavaMethod.getRelativeOffset(javaInstructionOffset: int): int;
    var
        index: int;
    begin
        if sourceCode = nil then begin
            result := -1;
            exit;
        end;
        index := sourceCode.indexOfInstruction(javaInstructionOffset);
        if index < 0 then begin
            result := -1;
            exit;
        end;
        result := computeCodeSize(commands, 0, commandIndices[index]);
    end;

    procedure JavaMethod.onAddAttribute(attribute: JavaAttribute);
    begin
        if (sourceCode = nil) and (attribute is JavaCode) then begin
            sourceCode := JavaCode(attribute);
        end;
    end;

    function JavaMethod.getAttributeClass(const name: AnsiString): JavaAttribute_Class;
    begin
        if (sourceCode = nil) and (name = 'Code') then begin
            result := JavaCode;
        end else begin
            result := nil;
        end;
    end;

    function JavaMethod.getCodeSize(): int;
    begin
        result := computeCodeSize(commands, 0, commandsCount);
        if (result and $03) <> 0 then begin
            result := (result and (-$04)) + $04;
        end;
    end;

    function JavaMethod.getParametersCount(): int;
    begin
        result := parametersCount;
    end;

    function JavaMethod.getVirtualIndex(): int;
    begin
        result := virtualIndex;
    end;

    function JavaMethod.getOverridden(): JavaMethod;
    begin
        if virtualStatus = VIRTUAL_STATUS_NOT_DEFINED then begin
            defineVirtualStatus();
        end;
        result := overridden;
    end;

    function JavaMethod.getLineNumberTable(): JavaLineNumberTable;
    begin
        if sourceCode <> nil then begin
            result := sourceCode.getLineNumberTable();
        end else begin
            result := nil;
        end;
    end;

    function JavaMethod.isSynchronized(): boolean;
    begin
        result := (getFlags() and FLAG_SYNCHRONIZED) <> 0;
    end;

    function JavaMethod.isStrictfp(): boolean;
    begin
        result := (getFlags() and FLAG_STRICTFP) <> 0;
    end;

    function JavaMethod.isAbstract(): boolean;
    begin
        result := (getFlags() and FLAG_ABSTRACT) <> 0;
    end;

    function JavaMethod.isVirtual(): boolean;
    begin
        if virtualStatus = VIRTUAL_STATUS_NOT_DEFINED then begin
            defineVirtualStatus();
        end;
        result := virtualStatus = VIRTUAL_STATUS_HAS_VIRTUAL;
    end;

    function JavaMethod.isNative(): boolean;
    begin
        result := (getFlags() and FLAG_NATIVE) <> 0;
    end;
{%endregion}

{%region JavaClass }
    class procedure JavaClass.writeClassReference(stream: DataOutput; cls: JavaClass);
    begin
        if cls <> nil then begin
            stream.writeIntLE(cls.getOffset());
        end else begin
            stream.writeIntLE(0);
        end;
    end;

    class function JavaClass.getClassSignature(const name: AnsiString): AnsiString;
    begin
        if (length(name) > 0) and (name[1] = PREFIX_ARRAY) then begin
            result := name;
        end else begin
            result := PREFIX_OBJECT + name + SUFFIX_OBJECT;
        end;
    end;

    constructor JavaClass.create(owner: JavaStaticRecompiler; const constantPool: ConstantPoolEntry_Array1d; flags: int; const name, ancestor: AnsiString; const interfaces: AnsiString_Array1d);
    var
        len: int;
        i: int;
        p: int;
        s: AnsiString;
        intf: InterfaceImplementsInfo_Array1d;
    begin
        inherited create(nil, flags, name, JavaClass.getClassSignature(name));
        len := length(name);
        p := 0;
        s := copy(name, 1, len);
        for i := len downto 1 do begin
            if name[i] = SEPARATOR_PACKAGES then begin
                s[i] := SEPARATOR_CANONICAL;
                if (name[1] <> PREFIX_ARRAY) and (p = 0) then begin
                    p := i;
                end;
            end;
        end;
        len := length(interfaces);
        intf := InterfaceImplementsInfo_Array1d_create(len);
        for i := len - 1 downto 0 do begin
            intf[i] := InterfaceImplementsInfo.create(interfaces[i]);
        end;
        self.owner := owner;
        self.packageName := copy(s, 1, max(0, p - 1));
        self.outputName := s;
        self.ancestorName := ancestor;
        self.constantPoolLength := length(constantPool);
        self.constantPool := constantPool;
        self.interfacesCount := length(interfaces);
        self.interfaces := intf;
    end;

    constructor JavaClass.create(owner: JavaStaticRecompiler; nameIndex: int);
    begin
        inherited create(nil, FLAG_PRIMITIVE + FLAG_FINAL + FLAG_PUBLIC, PRIMITIVES_NAMES[nameIndex], PRIMITIVES_PREFIXES[nameIndex]);
        self.owner := owner;
        self.packageName := '';
        self.outputName := PRIMITIVES_NAMES[nameIndex];
        self.ancestorName := CLASS_OBJECT;
        self.constantPoolLength := 0;
        self.constantPool := nil;
        self.interfacesCount := 0;
        self.interfaces := nil;
    end;

    destructor JavaClass.destroy;
    var
        i: int;
        cnpl: ConstantPoolEntry_Array1d;
        intf: InterfaceImplementsInfo_Array1d;
        flds: JavaField_Array1d;
        mthd: JavaMethod_Array1d;
    begin
        cnpl := constantPool;
        for i := constantPoolLength - 1 downto 0 do begin
            cnpl[i].free();
        end;
        intf := interfaces;
        for i := interfacesCount - 1 downto 0 do begin
            intf[i].free();
        end;
        flds := fields;
        for i := fieldsCount - 1 downto 0 do begin
            flds[i].free();
        end;
        mthd := methods;
        for i := methodsCount - 1 downto 0 do begin
            mthd[i].free();
        end;
        sourceFile.free();
        inherited destroy;
    end;

    procedure JavaClass.addMethod(method: JavaMethod);
    var
        count: int;
        methods: JavaMethod_Array1d;
    begin
        count := self.methodsCount;
        methods := self.methods;
        if count = length(methods) then begin
            methods := JavaMethod_Array1d_create(count + 16);
            arraycopy(self.methods, 0, methods, 0, count);
            self.methods := methods;
        end;
        methods[count] := method;
        self.methodsCount := count + 1;
    end;

    procedure JavaClass.addInterface(intf: JavaClass);
    var
        count: int;
        interfaces: InterfaceImplementsInfo_Array1d;
    begin
        count := self.interfacesCount;
        interfaces := self.interfaces;
        if count = length(interfaces) then begin
            interfaces := InterfaceImplementsInfo_Array1d_create(count + 4);
            arraycopy(self.interfaces, 0, interfaces, 0, count);
            self.interfaces := interfaces;
        end;
        interfaces[count] := InterfaceImplementsInfo.create(intf);
        self.interfacesCount := count + 1;
    end;

    function JavaClass.findVirtualMethod(virtualIndex: int): JavaMethod;
    var
        i: int;
        cls: JavaClass;
        method: JavaMethod;
        methods: JavaMethod_Array1d;
    begin
        cls := self;
        repeat
            methods := cls.methods;
            for i := cls.methodsCount - 1 downto 0 do begin
                method := methods[i];
                if (method.isVirtual()) and (method.getVirtualIndex() = virtualIndex) then begin
                    result := method;
                    exit;
                end;
            end;
            cls := cls.ancestor;
        until (cls = nil) or (cls = self);
        result := nil;
    end;

    function JavaClass.getRefFieldsOffsets(): int_Array1d;
    var
        i: int;
        len: int;
        old: int_Array1d;
        cls: JavaClass;
        field: JavaField;
        fields: JavaField_Array1d;
        signature: AnsiString;
    begin
        result := int_Array1d_create(15);
        len := 0;
        cls := self;
        repeat
            fields := cls.fields;
            for i := cls.fieldsCount - 1 downto 0 do begin
                field := fields[i];
                signature := field.getSignature();
                if not field.isStatic() and (length(signature) > 0) and (signature[1] in [PREFIX_OBJECT, PREFIX_ARRAY]) then begin
                    if len = length(result) then begin
                        old := result;
                        result := int_Array1d_create((len shl 1) + 1);
                        arraycopy(old, 0, result, 0, len);
                    end;
                    result[len] := field.getOffset();
                    inc(len);
                end;
            end;
            cls := cls.ancestor;
        until cls = nil;
        if len <> length(result) then begin
            old := result;
            result := int_Array1d_create(len);
            arraycopy(old, 0, result, 0, len);
        end;
    end;

    procedure JavaClass.findAncestors();
    var
        i: int;
        name: AnsiString;
        info: InterfaceImplementsInfo;
        intf: InterfaceImplementsInfo_Array1d;
        compiler: JavaStaticRecompiler;
    begin
        compiler := owner;
        name := ancestorName;
        if length(name) > 0 then begin
            ancestor := compiler.findClassByName(name);
        end;
        intf := interfaces;
        for i := interfacesCount - 1 downto 0 do begin
            info := intf[i];
            info.setInterface(compiler.findClassByName(info.getInterfaceName()));
        end;
    end;

    procedure JavaClass.defineImplementedMethods();
    var
        i: int;
        j: int;
        intf: JavaClass;
        subIntf: JavaClass;
        interfaceImplements: InterfaceImplementsInfo;
        methodImplements: InterfaceMethodImplements;
        method: JavaMethod;
        name: AnsiString;
        signature: AnsiString;
    begin
        if isInterface() then begin
            i := 0;
            while i < interfacesCount do begin
                intf := interfaces[i].getInterface();
                for j := intf.getInterfacesCount() - 1 downto 0 do begin
                    subIntf := intf.getInterface(j);
                    if not isInheritedFrom(subIntf) then begin
                        addInterface(subIntf);
                    end;
                end;
                inc(i);
            end;
            exit;
        end;
        i := 0;
        while i < interfacesCount do begin
            interfaceImplements := interfaces[i];
            for j := interfaceImplements.getMethodsCount() - 1 downto 0 do begin
                methodImplements := interfaceImplements.getMethodImplements(j);
                name := methodImplements.getMethodName();
                signature := methodImplements.getMethodSignature();
                method := findMember(name, signature) as JavaMethod;
                if method.getOwner().isInterface() then begin
                    method := JavaMethod.create(self, FLAG_PUBLIC + FLAG_ABSTRACT, name, signature);
                    addMethod(method);
                end;
                methodImplements.setClassMethod(method);
            end;
            intf := interfaceImplements.getInterface();
            for j := intf.getInterfacesCount() - 1 downto 0 do begin
                subIntf := intf.getInterface(j);
                if not isInheritedFrom(subIntf) then begin
                    addInterface(subIntf);
                end;
            end;
            inc(i);
        end;
    end;

    procedure JavaClass.defineVirtualMethods();
    var
        i: int;
        count: int;
        virtualIndex: int;
        ancestor: JavaClass;
        overridden: JavaMethod;
        method: JavaMethod;
        methods: JavaMethod_Array1d;
    begin
        virtualIndex := 0;
        count := self.methodsCount;
        methods := self.methods;
        if isInterface() then begin
            for i := 0 to count - 1 do begin
                method := methods[i];
                if method.isAbstract() then begin
                    method.setVirtualIndex(virtualIndex);
                    inc(virtualIndex);
                end;
            end;
            exit;
        end;
        ancestor := self.ancestor;
        if ancestor <> nil then begin
            virtualIndex := ancestor.virtualsCount;
        end;
        for i := 0 to count - 1 do begin
            method := methods[i];
            if not method.isVirtual() then begin
                continue;
            end;
            overridden := method.getOverridden();
            if overridden = nil then begin
                method.setVirtualIndex(virtualIndex);
                inc(virtualIndex);
            end else begin
                method.setVirtualIndex(overridden.getVirtualIndex());
            end;
        end;
        self.virtualsCount := virtualIndex;
    end;

    procedure JavaClass.defineFieldOffsets();
    var
        i: int;
        count: int;
        offset: int;
        ancestor: JavaClass;
        field: JavaField;
        fields: JavaField_Array1d;
        compiler: JavaStaticRecompiler;
    begin
        compiler := self.owner;
        ancestor := self.ancestor;
        if ancestor <> nil then begin
            offset := ancestor.instanceSize;
        end else begin
            offset := -4;
        end;
        count := self.fieldsCount;
        fields := self.fields;
        for i := 0 to count - 1 do begin
            field := fields[i];
            if field.isStatic() then begin
                continue;
            end;
            field.setOffset(offset);
            inc(offset, compiler.getTypeSize(field.getSignature()));
        end;
        self.instanceSize := offset;
    end;

    procedure JavaClass.writeIMTs(stream: DataOutput);
    var
        i: int;
        j: int;
        k: int;
        s: int;
        imtOffset: int;
        interfaces: InterfaceImplementsInfo_Array1d;
        implements: InterfaceImplementsInfo;
        methodimpl: InterfaceMethodImplements;
        classMethod: JavaMethod;
    begin
        if isInterface() then begin
            exit;
        end;
        interfaces := self.interfaces;
        for i := 0 to interfacesCount - 1 do begin
            implements := interfaces[i];
            imtOffset := implements.getIMTOffset();
            for j := 0 to implements.getMethodsCount() - 1 do begin
                methodimpl := implements.getMethodImplements(j);
                classMethod := methodimpl.getClassMethod();
                if classMethod.isVirtual() then begin
                    { load object [esp + classMethod.getParametersCount() * $10] }
                    stream.writeByte($47);
                    stream.writeShortLE(classMethod.getParametersCount());
                    { getclass }
                    stream.writeByte($cf);
                    { getfield object +$18 }
                    stream.writeIntLE($000018c7);
                    { load int classMethod.getVirtualIndex() }
                    stream.writeByte($0b);
                    stream.writeIntLE(classMethod.getVirtualIndex());
                    { getarraycell int }
                    stream.writeShort($ff0a);
                    { jmp int }
                    stream.writeByte($b8);
                    continue;
                end;
                s := classMethod.getOffset() - (imtOffset + j * IMT_ENTRY_SIZE + 2);
                if (s >= -$80) and (s < $80) then begin
                    { jmp ±$xx }
                    stream.writeByte($b0);
                    stream.writeByte(s);
                    for k := 2 to IMT_ENTRY_SIZE - 1 do begin
                        stream.writeByte($00);
                    end;
                    continue;
                end;
                dec(s);
                if (s >= -$8000) and (s < $8000) then begin
                    { jmp ±$jjii }
                    stream.writeByte($b1);
                    stream.writeShortLE(s);
                    for k := 3 to IMT_ENTRY_SIZE - 1 do begin
                        stream.writeByte($00);
                    end;
                    continue;
                end;
                { jmp ±$llkkjjii }
                stream.writeByte($b2);
                stream.writeIntLE(s - 2);
                for k := 5 to IMT_ENTRY_SIZE - 1 do begin
                    stream.writeByte($00);
                end;
            end;
        end;
    end;

    procedure JavaClass.writeData(stream: DataOutput);
    var
        method: JavaMethod;
        compiler: JavaStaticRecompiler;
        interfaces: InterfaceImplementsInfo_Array1d;
        intf: InterfaceImplementsInfo;
        cls: JavaClass;
        refOffsets: int_Array1d;
        classInstanceSize: int;
        refOffsetsCount: int;
        offset: int;
        i: int;
        j: int;
    begin
        compiler := getCompiler();
        cls := compiler.classClass;
        refOffsets := getRefFieldsOffsets();
        classInstanceSize := cls.getInstanceSize();
        refOffsetsCount := length(refOffsets);
        offset := getOffset() + classInstanceSize;
        { записываем инстанцию класса java.lang.Class }
        stream.writeIntLE(0);
        stream.writeIntLE(0);
        stream.writeIntLE(cls.getOffset());
        stream.writeIntLE(getFlags());
        stream.writeIntLE(getInstanceSize());
        stream.writeIntLE(nameIndex);
        stream.writeIntLE(offset + 4);
        stream.writeIntLE(offset + (20 + refOffsetsCount * 4) + 4);
        stream.writeIntLE(0);
        stream.writeIntLE(offset + (20 + refOffsetsCount * 4) + (20 + virtualsCount * 4) + 4);
        writeClassReference(stream, ancestor);
        writeClassReference(stream, getComponentClass());
        writeClassReference(stream, next);
        for i := $30 to classInstanceSize - 1 do begin
            stream.writeByte(0);
        end;
        { записываем инстанцию класса int[] (на неё ссылается поле refOffsets) }
        stream.writeIntLE(0);
        stream.writeIntLE(1);
        writeClassReference(stream, compiler.findClassBySignature(PREFIX_ARRAY + PREFIX_INT));
        stream.writeIntLE(refOffsetsCount);
        stream.writeIntLE(0);
        for i := 0 to refOffsetsCount - 1 do begin
            stream.writeIntLE(refOffsets[i]);
        end;
        { записываем инстанцию класса int[] (на неё ссылается поле virtualsAddresses) }
        stream.writeIntLE(0);
        stream.writeIntLE(1);
        writeClassReference(stream, compiler.findClassBySignature(PREFIX_ARRAY + PREFIX_INT));
        stream.writeIntLE(virtualsCount);
        stream.writeIntLE(0);
        for i := 0 to virtualsCount - 1 do begin
            method := findVirtualMethod(i);
            if method.isAbstract() then begin
                method := compiler.methodThrowAbstractMethodError as JavaMethod;
            end;
            stream.writeIntLE(method.getOffset());
        end;
        { записываем инстанцию класса java.lang.InterfaceEntry[] (на неё ссылается поле interfaces) }
        inc(offset, (20 + refOffsetsCount * 4) + (20 + virtualsCount * 4) + (20 + interfacesCount * 4));
        stream.writeIntLE(0);
        stream.writeIntLE(1);
        writeClassReference(stream, compiler.findClassBySignature(PREFIX_ARRAY + PREFIX_OBJECT + CLASS_INTERFACE_ENTRY + SUFFIX_OBJECT));
        stream.writeIntLE(interfacesCount);
        stream.writeIntLE(0);
        cls := compiler.findClassByName(CLASS_INTERFACE_ENTRY);
        j := cls.getInstanceSize() + 4;
        for i := 0 to interfacesCount - 1 do begin
            stream.writeIntLE(offset + (i * j) + 4);
        end;
        { записываем инстанции класса java.lang.InterfaceEntry }
        interfaces := self.interfaces;
        for i := 0 to interfacesCount - 1 do begin
            intf := interfaces[i];
            stream.writeIntLE(0);
            stream.writeIntLE(1);
            writeClassReference(stream, cls);
            stream.writeIntLE(intf.getIMTOffset());
            writeClassReference(stream, intf.getInterface());
        end;
    end;

    procedure JavaClass.setMembers(const fields: JavaField_Array1d; const methods: JavaMethod_Array1d);
    var
        f: boolean;
        i: int;
        m: JavaClassMember;
        s: AnsiString;
    begin
        f := false;
        for i := length(methods) - 1 downto 0 do begin
            m := methods[i];
            if (not m.isStatic()) and (m.getAccess() >= ACCESS_PROTECTED) and (m.getName() = NAME_FINALIZE) and (m.getSignature() = SIGNATURE_NO_PARAMETERS) then begin
                f := true;
                break;
            end;
        end;
        if not f then begin
            for i := length(fields) - 1 downto 0 do begin
                m := fields[i];
                s := m.getSignature();
                if (not m.isStatic()) and (length(s) > 0) and (s[1] in [PREFIX_ARRAY, PREFIX_OBJECT]) then begin
                    f := true;
                    break;
                end;
            end;
        end else begin
            f := false;
        end;
        self.fieldsCount := length(fields);
        self.fields := fields;
        if f then begin
            i := length(methods);
            self.methodsCount := i + 1;
            self.methods := JavaMethod_Array1d_create(i + 16);
            if i > 0 then begin
                arraycopy(methods, 0, self.methods, 0, i);
            end;
            m := JavaMethod.create(self, FLAG_PROTECTED, NAME_FINALIZE, SIGNATURE_NO_PARAMETERS);
            m.onAddAttribute(JavaCode.create(m, 'Code', toByteArray1d([
                byte($00), byte($00), byte($00), byte($01), byte($00), byte($00), byte($00), byte($01),
                byte($b1), byte($00), byte($00), byte($00), byte($00)
            ]), constantPool));
            self.methods[i] := JavaMethod(m);
        end else begin
            self.methodsCount := length(methods);
            self.methods := methods;
        end;
    end;

    procedure JavaClass.setNameIndex(nameIndex: int);
    begin
        self.nameIndex := nameIndex;
    end;

    procedure JavaClass.setNextClass(next: JavaClass);
    begin
        self.next := next;
    end;

    procedure JavaClass.setIMTOffset(interfaceIndex, imtOffset: int);
    begin
        if (interfaceIndex < 0) or (interfaceIndex >= interfacesCount) then begin
            raise ArrayIndexOutOfBoundsException.create(interfaceIndex);
        end;
        interfaces[interfaceIndex].setIMTOffset(imtOffset);
    end;

    function JavaClass.getIMTOffset(interfaceIndex: int): int;
    begin
        if (interfaceIndex < 0) or (interfaceIndex >= interfacesCount) then begin
            raise ArrayIndexOutOfBoundsException.create(interfaceIndex);
        end;
        result := interfaces[interfaceIndex].getIMTOffset();
    end;

    function JavaClass.findMember(const name, signature: AnsiString): JavaClassMember;
    var
        i: int;
        cls: JavaClass;
        intf: InterfaceImplementsInfo_Array1d;
    begin
        cls := self;
        repeat
            result := cls.findMemberInternal(name, signature);
            if result <> nil then begin
                exit;
            end;
            cls := cls.ancestor;
        until (cls = nil) or (cls = self);
        cls := self;
        repeat
            intf := cls.interfaces;
            for i := cls.interfacesCount - 1 downto 0 do begin
                result := intf[i].getInterface().findMemberInternal(name, signature);
                if result <> nil then begin
                    exit;
                end;
            end;
            cls := cls.ancestor;
        until (cls = nil) or (cls = self);
    end;

    function JavaClass.findMemberInternal(const name, signature: AnsiString): JavaClassMember;
    var
        i: int;
        member: JavaClassMember;
        fields: JavaField_Array1d;
        methods: JavaMethod_Array1d;
    begin
        if (length(signature) > 0) and (signature[1] = HEADER_PREFIX) then begin
            methods := self.methods;
            for i := methodsCount - 1 downto 0 do begin
                member := methods[i];
                if (member.getName() = name) and (member.getSignature() = signature) then begin
                    result := member;
                    exit;
                end;
            end;
        end else begin
            fields := self.fields;
            for i := fieldsCount - 1 downto 0 do begin
                member := fields[i];
                if (member.getName() = name) and (member.getSignature() = signature) then begin
                    result := member;
                    exit;
                end;
            end;
        end;
        result := nil;
    end;

    procedure JavaClass.onAddAttribute(attribute: JavaAttribute);
    begin
        if (sourceFile = nil) and (attribute is JavaConstantValue) then begin
            sourceFile := JavaConstantValue(attribute);
        end;
    end;

    function JavaClass.getAttributeClass(const name: AnsiString): JavaAttribute_Class;
    begin
        if (sourceFile = nil) and (name = 'SourceFile') then begin
            result := JavaConstantValue;
        end else begin
            result := nil;
        end;
    end;

    function JavaClass.getCompiler(): JavaStaticRecompiler;
    begin
        result := owner;
    end;

    function JavaClass.getComponentClass(): JavaClass;
    begin
        result := nil;
    end;

    function JavaClass.getInstanceSize(): int;
    begin
        if isInterface() then begin
            result := 0;
        end else begin
            result := instanceSize;
        end;
    end;

    function JavaClass.getLongIndex(value: long): int;
    var
        i: int;
        count: int;
        constantPool: ConstantPoolEntry_Array1d;
        entry: ConstantPoolEntry;
    begin
        count := self.constantPoolLength;
        constantPool := self.constantPool;
        for i := count - 1 downto 0 do begin
            entry := constantPool[i];
            if (entry is LongEntry) and (LongEntry(entry).longValue() = value) then begin
                result := i;
                exit;
            end;
        end;
        if count = length(constantPool) then begin
            constantPool := ConstantPoolEntry_Array1d_create(count * 2 + 1);
            arraycopy(self.constantPool, 0, constantPool, 0, count);
            self.constantPool := constantPool;
        end;
        constantPool[count] := LongEntry.create(value);
        self.constantPoolLength := count + 1;
        result := count;
    end;

    function JavaClass.getDoubleIndex(value: double): int;
    var
        i: int;
        count: int;
        bits: long;
        constantPool: ConstantPoolEntry_Array1d;
        entry: ConstantPoolEntry;
    begin
        count := self.constantPoolLength;
        constantPool := self.constantPool;
        bits := doubleToLongBits(value);
        for i := count - 1 downto 0 do begin
            entry := constantPool[i];
            if (entry is DoubleEntry) and (doubleToLongBits(DoubleEntry(entry).doubleValue()) = bits) then begin
                result := i;
                exit;
            end;
        end;
        if count = length(constantPool) then begin
            constantPool := ConstantPoolEntry_Array1d_create(count * 2 + 1);
            arraycopy(self.constantPool, 0, constantPool, 0, count);
            self.constantPool := constantPool;
        end;
        constantPool[count] := DoubleEntry.create(value);
        self.constantPoolLength := count + 1;
        result := count;
    end;

    function JavaClass.getStringIndex(const value: AnsiString): int;
    var
        i: int;
        count: int;
        constantPool: ConstantPoolEntry_Array1d;
        entry: ConstantPoolEntry;
    begin
        count := self.constantPoolLength;
        constantPool := self.constantPool;
        for i := count - 1 downto 0 do begin
            entry := constantPool[i];
            if (entry is StringEntry) and (StringEntry(entry).stringValue() = value) then begin
                result := i;
                exit;
            end;
        end;
        if count = length(constantPool) then begin
            constantPool := ConstantPoolEntry_Array1d_create(count * 2 + 1);
            arraycopy(self.constantPool, 0, constantPool, 0, count);
            self.constantPool := constantPool;
        end;
        constantPool[count] := StringEntry.create(value);
        self.constantPoolLength := count + 1;
        result := count;
    end;

    function JavaClass.getConstantPoolLength(): int;
    begin
        result := constantPoolLength;
    end;

    function JavaClass.getConstantPoolEntry(index: int): ConstantPoolEntry;
    begin
        if (index < -5) or (index >= constantPoolLength) then begin
            raise ArrayIndexOutOfBoundsException.create(index);
        end;
        if index >= 0 then begin
            result := constantPool[index];
        end else begin
            result := owner.getConstantPoolEntry(index);
        end;
    end;

    function JavaClass.getConstantPoolMember(index, memberType: int): JavaClassMember;
    var
        e1: ConstantPoolEntry;
        e2: ConstantPoolEntry;
    begin
        e2 := getConstantPoolEntry(index);
        e1 := getConstantPoolEntry((e2 as DoubleIndexedEntry).getIndex1());
        e2 := getConstantPoolEntry((e2 as DoubleIndexedEntry).getIndex2());
        result := getConstantPoolMember((e1 as SingleIndexedEntry).getIndex1(), (e2 as DoubleIndexedEntry).getIndex1(), (e2 as DoubleIndexedEntry).getIndex2(), memberType);
    end;

    function JavaClass.getConstantPoolMember(classNameIndex, memberNameIndex, memberSignatureIndex, memberType: int): JavaClassMember;
    var
        constantPool: ConstantPoolEntry_Array1d;
        className: AnsiString;
        memberName: AnsiString;
        memberSignature: AnsiString;
    begin
        constantPool := self.constantPool;
        className := (constantPool[classNameIndex] as StringEntry).stringValue();
        memberName := (constantPool[memberNameIndex] as StringEntry).stringValue();
        memberSignature := (constantPool[memberSignatureIndex] as StringEntry).stringValue();
        result := getCompiler().findClassByName(className).findMember(memberName, memberSignature);
        if result = nil then begin
            if (memberType = MEMBER_TYPE_STATIC_FIELD) or (memberType = MEMBER_TYPE_INSTANCE_FIELD) then begin
                memberSignature := ': ' + memberSignature;
            end;
            raise ClassMemberNotFoundException.create('Не удалось найти ' + MEMBER_TYPES[memberType] + ': ' + className + '.' + memberName + memberSignature);
        end;
    end;

    function JavaClass.getPackageName(): AnsiString;
    begin
        result := packageName;
    end;

    function JavaClass.getOutputName(): AnsiString;
    begin
        result := outputName;
    end;

    function JavaClass.getAncestor(): JavaClass;
    begin
        result := ancestor;
    end;

    function JavaClass.getInterfacesCount(): int;
    begin
        result := interfacesCount;
    end;

    function JavaClass.getInterfaceImplements(index: int): InterfaceImplementsInfo;
    begin
        if (index < 0) or (index >= interfacesCount) then begin
            raise ArrayIndexOutOfBoundsException.create(index);
        end;
        result := interfaces[index];
    end;

    function JavaClass.getInterface(index: int): JavaClass;
    begin
        if (index < 0) or (index >= interfacesCount) then begin
            raise ArrayIndexOutOfBoundsException.create(index);
        end;
        result := interfaces[index].getInterface();
    end;

    function JavaClass.getAbstractMethodsCount(): int;
    var
        i: int;
        m: JavaMethod_Array1d;
    begin
        result := 0;
        m := methods;
        for i := methodsCount - 1 downto 0 do begin
            if m[i].isAbstract() then begin
                inc(result);
            end;
        end;
    end;

    function JavaClass.getMethodsCount(): int;
    begin
        result := methodsCount;
    end;

    function JavaClass.getMethod(index: int): JavaMethod;
    begin
        if (index < 0) or (index >= methodsCount) then begin
            raise ArrayIndexOutOfBoundsException.create(index);
        end;
        result := methods[index];
    end;

    function JavaClass.getFieldsCount(): int;
    begin
        result := fieldsCount;
    end;

    function JavaClass.getField(index: int): JavaField;
    begin
        if (index < 0) or (index >= fieldsCount) then begin
            raise ArrayIndexOutOfBoundsException.create(index);
        end;
        result := fields[index];
    end;

    function JavaClass.getSourceFile(): AnsiString;
    var
        i: int;
        s: JavaConstantValue;
        e: ConstantPoolEntry;
    begin
        s := sourceFile;
        if s <> nil then begin
            i := s.getConstantPoolIndex();
            if (i >= 0) and (i < constantPoolLength) then begin
                e := constantPool[i];
                if e is StringEntry then begin
                    result := StringEntry(e).stringValue();
                    exit;
                end;
            end;
        end;
        result := UNKNOWN_SOURCE;
    end;

    function JavaClass.getDataSize(): int;
    var
        compiler: JavaStaticRecompiler;
    begin
        compiler := owner;
        result := (4 + compiler.classClass.instanceSize) + (20 + (length(getRefFieldsOffsets()) shl 2)) + (20 + (virtualsCount shl 2)) + (20 + (compiler.classInterfaceEntry.instanceSize + 8) * interfacesCount);
    end;

    function JavaClass.isDirectSuperClass(ancestor: JavaClass): boolean;
    var
        i: int;
    begin
        if (ancestor <> nil) and (ancestor.isInterface()) then begin
            for i := interfacesCount - 1 downto 0 do begin
                if interfaces[i].getInterface() = ancestor then begin
                    result := true;
                    exit;
                end;
            end;
            result := false;
            exit;
        end;
        result := self.ancestor = ancestor;
    end;

    function JavaClass.isInheritedFrom(cls: JavaClass): boolean;
    var
        i: int;
        c: JavaClass;
        intf: InterfaceImplementsInfo_Array1d;
    begin
        result := cls = self;
        if not result then begin
            c := self;
            if cls.isInterface() then begin
                repeat
                    intf := c.interfaces;
                    for i := c.interfacesCount - 1 downto 0 do begin
                        if intf[i].getInterface() = cls then begin
                            result := true;
                            exit;
                        end;
                    end;
                    c := c.ancestor;
                until (c = nil) or (c = self);
            end else begin
                repeat
                    if c = cls then begin
                        result := true;
                        exit;
                    end;
                    c := c.ancestor;
                until (c = nil) or (c = self);
            end;
        end;
    end;

    function JavaClass.isPrimitive(): boolean;
    begin
        result := (getFlags() and FLAG_PRIMITIVE) <> 0;
    end;

    function JavaClass.isInterface(): boolean;
    begin
        result := (getFlags() and FLAG_INTERFACE) <> 0;
    end;

    function JavaClass.isAbstract(): boolean;
    begin
        result := (getFlags() and FLAG_ABSTRACT) <> 0;
    end;
{%endregion}

{%region JavaArray }
    constructor JavaArray.create(owner: JavaStaticRecompiler; componentClass: JavaClass);
    begin
        inherited create(owner, nil, (FLAG_FINAL or FLAG_STATIC) or (componentClass.getFlags() and (FLAG_PUBLIC or FLAG_PRIVATE or FLAG_PROTECTED)), PREFIX_ARRAY + componentClass.getSignature(), CLASS_OBJECT, nil);
        self.componentClass := componentClass;
    end;

    function JavaArray.getComponentClass(): JavaClass;
    begin
        result := componentClass;
    end;

    function JavaArray.getInstanceSize(): int;
    begin
        result := 16;
    end;

    function JavaArray.getCellClass(): JavaClass;
    var
        cls: JavaClass;
    begin
        result := cellClass;
        if result = nil then begin
            cls := componentClass;
            while cls is JavaArray do begin
                cls := JavaArray(cls).componentClass;
            end;
            cellClass := cls;
            result := cls;
        end;
    end;

    function JavaArray.getDimensionsCount(): int;
    var
        cls: JavaClass;
    begin
        result := dimensionsCount;
        if result = 0 then begin
            result := 1;
            cls := componentClass;
            while cls is JavaArray do begin
                cls := JavaArray(cls).componentClass;
                inc(result);
            end;
            dimensionsCount := result;
        end;
    end;
{%endregion}

{%region JavaStaticRecompiler }
    procedure JavaStaticRecompiler.writeToTextDebugInfoFile(stream: DataOutput; cls: JavaClass);
    begin
        stream.writeChars(globalOffsetToString(cls.getOffset()) + '=class: ' + cls.getSignature());
        cls := cls.getAncestor();
        if cls <> nil then begin
            stream.writeChars(' : ' + cls.getSignature() + LINE_ENDING);
        end else begin
            stream.writeChars(LINE_ENDING);
        end;
    end;

    constructor JavaStaticRecompiler.create();
    var
        manifestInput: Input;
        manifest: ProgrammeManifest;
        coreJarArchive: ZIPArchiveReader;
        coreJarFileName: AnsiString;
        configuration: AnsiString;
        profile: AnsiString;
        i: int;
    begin
        inherited create();
        coreDirectory := '';
        coreJarFileName := parseParams()[0];
        for i := length(coreJarFileName) downto 1 do begin
            if coreJarFileName[i] = DIRECTORY_SEPARATOR then begin
                coreDirectory := copy(coreJarFileName, 1, i) + ('java' + DIRECTORY_SEPARATOR);
                coreJarFileName := coreDirectory + 'malik-core.jar';
                break;
            end;
        end;
        coreJarArchive := ZIPArchiveReader.create(FileInputStream.create(coreJarFileName));
        try
            manifestInput := coreJarArchive.openFile(MANIFEST_FILE_NAME);
            if manifestInput <> nil then begin
                manifest := ProgrammeManifest.create();
                try
                    manifest.loadFromStream(manifestInput);
                    configuration := manifest.getValue(MANIFEST_PROPERTY_MIDLET_CONFIGURATION);
                    profile := manifest.getValue(MANIFEST_PROPERTY_MIDLET_PROFILE);
                    if (length(configuration) > 0) and (length(profile) > 0) then begin
                        platformName := 'Java (J2ME, ' + configuration + ', ' + profile + ')';
                    end else begin
                        platformName := 'Java (неопределённой редакции)';
                    end;
                finally
                    manifest.free();
                end;
            end else begin
                platformName := 'Java (неопределённой редакции)';
            end;
        finally
            coreJarArchive.free();
        end;
        init();
    end;

    constructor JavaStaticRecompiler.create(const coreDirectory, platformName: AnsiString);
    begin
        inherited create();
        self.coreDirectory := coreDirectory;
        self.platformName := platformName;
        init();
    end;

    destructor JavaStaticRecompiler.destroy;
    var
        i: int;
        a: ConstantPoolEntry_Array1d;
        p: JavaClass_Array1d;
    begin
        p := primitives;
        for i := length(p) - 1 downto 0 do begin
            p[i].free();
        end;
        a := fdconst;
        for i := length(a) - 1 downto 0 do begin
            a[i].free();
        end;
        strings.free();
        initOrder.free();
        inherited destroy;
    end;

    procedure JavaStaticRecompiler.init();
    var
        i: int;
        p: JavaClass_Array1d;
    begin
        i := length(PRIMITIVES_NAMES);
        p := JavaClass_Array1d_create(i);
        for i := i - 1 downto 0 do begin
            p[i] := JavaClass.create(self, i);
        end;
        primitives := p;
        fdconst := toConstantPoolEntryArray1d([
            FloatEntry.create(0.0), FloatEntry.create(1.0), FloatEntry.create(2.0),
            DoubleEntry.create(0.0), DoubleEntry.create(1.0)
        ]);
        strings := StringPool.create();
        initOrder := StringPool.create();
        setDefaults();
    end;

    procedure JavaStaticRecompiler.loadClassesFrom(archive: ZIPArchiveReader);
    begin
        archive.gotoFirstFile();
        while not archive.isStayOnEnd() do begin
            if endsWith(CLASS_FILE_EXTENSION, toLowerCase(archive.getCurrentFileName())) then begin
                loadClass(archive.openCurrentFile());
            end else begin
                archive.gotoNextFile();
            end;
        end;
    end;

    procedure JavaStaticRecompiler.loadClass(source: Input);
    var
        stream: DataInput;
        count: int;
        i: int;
        j: int;
        version: int;
        constantPool: ConstantPoolEntry_Array1d;
        flags: int;
        name: AnsiString;
        ancestor: AnsiString;
        interfaces: AnsiString_Array1d;
        fields: JavaField_Array1d;
        methods: JavaMethod_Array1d;
        cls: JavaClass;
        entryType: int;
        utf8Constant: AnsiString;
        intConstant: int;
        longConstant: long;
        index1: int;
        index2: int;
        memberFlags: int;
        memberName: AnsiString;
        memberSignature: AnsiString;
        classes: JavaClass_Array1d;
    begin
        { Незначительная уязвимость (возможно уже исправлено): если будет сгенерировано исключение, то возможна утечка памяти. }
        stream := DataInputStream.create(source);
        { читаем сигнатуру CLASS-файла }
        if stream.readInt() <> int($cafebabe) then begin
            exit;
        end;
        { читаем версию (максимальная поддерживаемая версия CLASS-файла = 48.0) }
        version := stream.readUnsignedShort();
        version := (stream.readUnsignedShort() shl 16) + version;
        if (version < 0) or (version > $00300000) then begin
            raise UnsupportedClassVersionException.create('Неподдерживаемая версия class-файла: ' + toDecString(long(version shr 16)) + '.' + toDecString(long(version and $ffff)) + '.');
        end;
        cls := nil;
        try
            { читаем константы }
            count := stream.readUnsignedShort();
            constantPool := ConstantPoolEntry_Array1d_create(count);
            i := 1;
            while i < count do begin
                entryType := stream.readByte();
                case entryType of
                ENTRY_UTF8: begin
                    utf8Constant := toUTF8String(stream.readUTF());
                    constantPool[i] := StringEntry.create(utf8Constant);
                end;
                ENTRY_INTEGER: begin
                    intConstant := stream.readInt();
                    constantPool[i] := IntegerEntry.create(intConstant);
                end;
                ENTRY_FLOAT: begin
                    intConstant := stream.readInt();
                    constantPool[i] := FloatEntry.create(intBitsToFloat(intConstant));
                end;
                ENTRY_LONG: begin
                    longConstant := stream.readLong();
                    constantPool[i] := LongEntry.create(longConstant);
                    inc(i);
                end;
                ENTRY_DOUBLE: begin
                    longConstant := stream.readLong();
                    constantPool[i] := DoubleEntry.create(longBitsToDouble(longConstant));
                    inc(i);
                end;
                ENTRY_CLASS, ENTRY_STRING: begin
                    index1 := stream.readUnsignedShort();
                    constantPool[i] := SingleIndexedEntry.create(entryType, index1);
                end;
                ENTRY_FIELD_REF, ENTRY_CLASS_METHOD_REF, ENTRY_INTERFACE_METHOD_REF, ENTRY_NAME_AND_SIGNATURE: begin
                    index1 := stream.readUnsignedShort();
                    index2 := stream.readUnsignedShort();
                    constantPool[i] := DoubleIndexedEntry.create(entryType, index1, index2);
                end;
                end;
                inc(i);
            end;
            { читаем флаги }
            flags := stream.readUnsignedShort();
            { читаем имя класса }
            i := stream.readUnsignedShort();
            if (i >= length(constantPool)) or (not (constantPool[i] is SingleIndexedEntry)) then begin
                raise ClassFileInvalidFormatException.create('Неправильный формат class-файла: нельзя определить имя класса.');
            end;
            i := SingleIndexedEntry(constantPool[i]).getIndex1();
            if (i >= length(constantPool)) or (not (constantPool[i] is StringEntry)) then begin
                raise ClassFileInvalidFormatException.create('Неправильный формат class-файла: нельзя определить имя класса.');
            end;
            name := StringEntry(constantPool[i]).stringValue();
            { читаем имя класса-предка }
            i := stream.readUnsignedShort();
            if i >= length(constantPool) then begin
                raise ClassFileInvalidFormatException.create('Неправильный формат class-файла: нельзя определить имя класса-предка.');
            end;
            if i > 0 then begin
                if not (constantPool[i] is SingleIndexedEntry) then begin
                    raise ClassFileInvalidFormatException.create('Неправильный формат class-файла: нельзя определить имя класса-предка.');
                end;
                i := SingleIndexedEntry(constantPool[i]).getIndex1();
                if (i >= length(constantPool)) or (not (constantPool[i] is StringEntry)) then begin
                    raise ClassFileInvalidFormatException.create('Неправильный формат class-файла: нельзя определить имя класса-предка.');
                end;
                ancestor := StringEntry(constantPool[i]).stringValue();
            end else begin
                ancestor := '';
            end;
            { читаем реализуемые протоколы }
            count := stream.readUnsignedShort();
            interfaces := String_Array1d_create(count);
            for j := 0 to count - 1 do begin
                i := stream.readUnsignedShort();
                if (i >= length(constantPool)) or (not (constantPool[i] is SingleIndexedEntry)) then begin
                    raise ClassFileInvalidFormatException.create('Неправильный формат class-файла: нельзя определить имя протокола.');
                end;
                i := SingleIndexedEntry(constantPool[i]).getIndex1();
                if (i >= length(constantPool)) or (not (constantPool[i] is StringEntry)) then begin
                    raise ClassFileInvalidFormatException.create('Неправильный формат class-файла: нельзя определить имя протокола.');
                end;
                interfaces[j] := StringEntry(constantPool[i]).stringValue();
            end;
            cls := JavaClass.create(self, constantPool, flags, name, ancestor, interfaces);
            { читаем поля }
            count := stream.readUnsignedShort();
            fields := JavaField_Array1d_create(count);
            for j := 0 to count - 1 do begin
                memberFlags := stream.readUnsignedShort();
                i := stream.readUnsignedShort();
                if (i >= length(constantPool)) or (not (constantPool[i] is StringEntry)) then begin
                    raise ClassFileInvalidFormatException.create('Неправильный формат class-файла: нельзя определить имя поля.');
                end;
                memberName := StringEntry(constantPool[i]).stringValue();
                i := stream.readUnsignedShort();
                if (i >= length(constantPool)) or (not (constantPool[i] is StringEntry)) then begin
                    raise ClassFileInvalidFormatException.create('Неправильный формат class-файла: нельзя определить тип поля.');
                end;
                memberSignature := StringEntry(constantPool[i]).stringValue();
                fields[j] := JavaField.create(cls, memberFlags, memberName, memberSignature);
                JavaAttribute.readAttributes(fields[j], stream, constantPool);
            end;
            { читаем методы }
            count := stream.readUnsignedShort();
            methods := JavaMethod_Array1d_create(count);
            for j := 0 to count - 1 do begin
                memberFlags := stream.readUnsignedShort();
                i := stream.readUnsignedShort();
                if (i >= length(constantPool)) or (not (constantPool[i] is StringEntry)) then begin
                    raise ClassFileInvalidFormatException.create('Неправильный формат class-файла: нельзя определить имя метода.');
                end;
                memberName := StringEntry(constantPool[i]).stringValue();
                i := stream.readUnsignedShort();
                if (i >= length(constantPool)) or (not (constantPool[i] is StringEntry)) then begin
                    raise ClassFileInvalidFormatException.create('Неправильный формат class-файла: нельзя определить сигнатуру метода.');
                end;
                memberSignature := StringEntry(constantPool[i]).stringValue();
                methods[j] := JavaMethod.create(cls, memberFlags, memberName, memberSignature);
                JavaAttribute.readAttributes(methods[j], stream, constantPool);
            end;
        except
            if cls = nil then begin
                for i := length(constantPool) - 1 downto 0 do begin
                    constantPool[i].free();
                end;
            end;
            for i := length(fields) - 1 downto 0 do begin
                fields[i].free();
            end;
            for i := length(methods) - 1 downto 0 do begin
                methods[i].free();
            end;
            cls.free();
            raise;
        end;
        try
            { читаем атрибуты класса }
            cls.setMembers(fields, methods);
            JavaAttribute.readAttributes(cls, stream, constantPool);
        except
            cls.free();
            raise;
        end;
        { добавляем класс в список }
        classes := self.classes;
        count := self.classesCount;
        if count = length(classes) then begin
            classes := JavaClass_Array1d_create(count * 2 + 1);
            arraycopy(self.classes, 0, classes, 0, count);
            self.classes := classes;
        end;
        classes[count] := cls;
        self.classesCount := count + 1;
    end;

    procedure JavaStaticRecompiler.addArray(arr: JavaArray);
    var
        count: int;
        arrays: JavaArray_Array1d;
    begin
        count := self.arraysCount;
        arrays := self.arrays;
        if count = length(arrays) then begin
            arrays := JavaArray_Array1d_create(count * 2 + 1);
            arraycopy(self.arrays, 0, arrays, 0, count);
            self.arrays := arrays;
        end;
        arrays[count] := arr;
        self.arraysCount := count + 1;
    end;

    procedure JavaStaticRecompiler.findAncestors();
    var
        i: int;
        classes: JavaClass_Array1d;
    begin
        classes := self.primitives;
        for i := length(classes) - 1 downto 0 do begin
            classes[i].findAncestors();
        end;
        classes := self.classes;
        for i := self.classesCount - 1 downto 0 do begin
            classes[i].findAncestors();
        end;
    end;

    procedure JavaStaticRecompiler.findSystemMembers();
    begin
        classObject := findClassByName(CLASS_OBJECT);
        classClass := findClassByName(CLASS_CLASS);
        classInterfaceEntry := findClassByName(CLASS_INTERFACE_ENTRY);
        classMalikSystem := findClassByName(CLASS_MALIK_SYSTEM);
        classMath := findClassByName(CLASS_MATH);
        interfaceInterrupt := findClassByName(INTERFACE_INTERRUPT);
        methodDefaultConstructor := findSystemMember(MEMBER_DEFAULT_CONSTRUCTOR);
        methodGetString := findSystemMember(MEMBER_GET_STRING);
        methodCheckObjectArrayAssignable := findSystemMember(MEMBER_CHECK_OBJECT_ARRAY_ASSIGNABLE);
        methodGetInterfaceMethodEntryPoint := findSystemMember(MEMBER_GET_INTERFACE_METHOD_ENTRY_POINT);
        methodAllocateInstance := findSystemMember(MEMBER_ALLOCATE_INSTANCE);
        methodArrayCreateOneDim := findSystemMember(MEMBER_ARRAY_CREATE_ONE_DIM);
        methodArrayCreateMultiDim := findSystemMember(MEMBER_ARRAY_CREATE_MULTI_DIM);
        methodCast := findSystemMember(MEMBER_CAST);
        methodIsInstance := findSystemMember(MEMBER_IS_INSTANCE);
        methodMonitorEnter := findSystemMember(MEMBER_MONITOR_ENTER);
        methodMonitorExit := findSystemMember(MEMBER_MONITOR_EXIT);
        methodThrowAbstractMethodError := findSystemMember(MEMBER_THROW_ABSTRACT);
        methodMain := findSystemMember(MEMBER_MAIN_PROCEDURE);
    end;

    procedure JavaStaticRecompiler.sortClasses();
    label
        label0;
    var
        i: int;
        j: int;
        lim: int;
        class1: JavaClass;
        class2: JavaClass;
        classes: JavaClass_Array1d;
    begin
        lim := self.classesCount - 1;
        classes := self.classes;
        for i := 0 to lim do begin
            label0:
            class1 := classes[i];
            for j := i + 1 to lim do begin
                class2 := classes[j];
                if class1.isDirectSuperClass(class2) then begin
                    arraycopy(classes, i, classes, i + 1, j - i);
                    classes[i] := class2;
                    goto label0;
                end;
            end;
        end;
    end;

    procedure JavaStaticRecompiler.preferenceForClassMembers();
    var
        i: int;
        count: int;
        cls: JavaClass;
        classes: JavaClass_Array1d;
    begin
        count := self.classesCount;
        classes := self.classes;
        for i := 0 to count - 1 do begin
            cls := classes[i];
            cls.defineImplementedMethods();
            cls.defineVirtualMethods();
            cls.defineFieldOffsets();
        end;
    end;

    procedure JavaStaticRecompiler.prepareMethods();
    var
        i: int;
        j: int;
        k: int;
        count: int;
        classes: JavaClass_Array1d;
        cls: JavaClass;
    begin
        count := self.classesCount;
        classes := self.classes;
        for i := 0 to count - 1 do begin
            cls := classes[i];
            k := cls.getMethodsCount();
            for j := 0 to k - 1 do begin
                cls.getMethod(j).prepareCompile();
            end;
        end;
    end;

    procedure JavaStaticRecompiler.prepareEntry();
    var
        c: int;
        i: int;
        initOrder: StringPool;
        commands: long_Array1d;
    begin
        initOrder := self.initOrder;
        for i := initOrder.getLength() - 1 downto 0 do begin
            if findClassByName(initOrder.getString(i)).findMemberInternal(NAME_INITIALIZATION, SIGNATURE_NO_PARAMETERS) = nil then begin
                initOrder.delete(i);
            end;
        end;
        c := initOrder.getLength();
        commands := long_Array1d_create(c + 4);
        self.entryCommands := commands;
        commands[0] := long($5008) + ((long(c) + 1) shl 16) { load except (+(c + 1) команд) };
        for i := 1 to c do begin
            commands[i] := long($5f10) + ((long(i) - 1) shl 16) { call static initOrder[i].<clinit>() };
        end;
        commands[c + 1] := long($5f0f) { call static Run.main() };
        commands[c + 2] := long($1010) { pop };
        commands[c + 3] := long($10bc) { ret };
    end;

    procedure JavaStaticRecompiler.linkClasses();
    var
        i: int;
        c: int;
        classes: JavaClass_Array1d;
        arrays: JavaArray_Array1d;
        primitives: JavaClass_Array1d;
    begin
        findClassBySignature(PREFIX_ARRAY + PREFIX_BYTE);
        findClassBySignature(PREFIX_ARRAY + PREFIX_INT);
        findClassBySignature(PREFIX_ARRAY + PREFIX_OBJECT + CLASS_INTERFACE_ENTRY + SUFFIX_OBJECT);
        classes := self.classes;
        arrays := self.arrays;
        primitives := self.primitives;
        c := classesCount - 1;
        for i := 0 to c do begin
            if i < c then begin
                classes[i].setNextClass(classes[i + 1]);
            end else begin
                classes[i].setNextClass(arrays[0]);
            end;
        end;
        c := arraysCount - 1;
        for i := 0 to c do begin
            if i < c then begin
                arrays[i].setNextClass(arrays[i + 1]);
            end else begin
                arrays[i].setNextClass(primitives[0]);
            end;
        end;
        c := length(primitives) - 1;
        for i := 0 to c do begin
            if i < c then begin
                primitives[i].setNextClass(primitives[i + 1]);
            end else begin
                primitives[i].setNextClass(nil);
            end;
        end;
    end;

    procedure JavaStaticRecompiler.extractStrings();
    var
        i: int;
        j: int;
        arrays: JavaArray_Array1d;
        classes: JavaClass_Array1d;
        strings: StringPool;
        cls: JavaClass;
    begin
        strings := self.strings;
        classes := self.primitives;
        for i := 0 to length(classes) - 1 do begin
            cls := classes[i];
            cls.setNameIndex(strings.acquireStringIndex(cls.getOutputName()));
        end;
        classes := self.classes;
        for i := 0 to classesCount - 1 do begin
            cls := classes[i];
            cls.setNameIndex(strings.acquireStringIndex(cls.getOutputName()));
        end;
        arrays := self.arrays;
        for i := 0 to arraysCount - 1 do begin
            cls := arrays[i];
            cls.setNameIndex(strings.acquireStringIndex(cls.getOutputName()));
        end;
        classes := self.classes;
        for i := 0 to classesCount - 1 do begin
            cls := classes[i];
            for j := 0 to cls.getMethodsCount() - 1 do begin
                cls.getMethod(j).fillStringPool();
            end;
        end;
    end;

    procedure JavaStaticRecompiler.assignOffsets();
    var
        i: int;
        j: int;
        offset: int;
        primitives: JavaClass_Array1d;
        classes: JavaClass_Array1d;
        arrays: JavaArray_Array1d;
        member: JavaClassMember;
        cls: JavaClass;
    begin
        offset := MalikProcessor.MEMORY_START + MalikProcessor.INTERRUPTS_TABLE_SIZE;
        primitives := self.primitives;
        classes := self.classes;
        arrays := self.arrays;
        self.imageBeginOffset := offset;
        { определяем смещения таблиц методов протоколов }
        for i := 0 to classesCount - 1 do begin
            cls := classes[i];
            if cls.isInterface() then begin
                continue;
            end;
            for j := 0 to cls.getInterfacesCount() - 1 do begin
                cls.setIMTOffset(j, offset);
                inc(offset, IMT_ENTRY_SIZE * cls.getInterface(j).getAbstractMethodsCount());
            end;
        end;
        for i := 0 to arraysCount - 1 do begin
            cls := arrays[i];
            for j := 0 to cls.getInterfacesCount() - 1 do begin
                cls.setIMTOffset(j, offset);
                inc(offset, IMT_ENTRY_SIZE * cls.getInterface(j).getAbstractMethodsCount());
            end;
        end;
        { определяем смещения статичных полей }
        for i := 0 to classesCount - 1 do begin
            cls := classes[i];
            for j := 0 to cls.getFieldsCount() - 1 do begin
                member := cls.getField(j);
                if (not member.isStatic()) or (not JavaField(member).isOutToExecutable()) then begin
                    continue;
                end;
                member.setOffset(offset);
                inc(offset, getTypeSize(member.getSignature(), true));
            end;
        end;
        { определяем смещения классов }
        for i := 0 to classesCount - 1 do begin
            cls := classes[i];
            cls.setOffset(offset + 4);
            inc(offset, cls.getDataSize());
        end;
        for i := 0 to arraysCount - 1 do begin
            cls := arrays[i];
            cls.setOffset(offset + 4);
            inc(offset, cls.getDataSize());
        end;
        for i := 0 to length(primitives) - 1 do begin
            cls := primitives[i];
            cls.setOffset(offset + 4);
            inc(offset, cls.getDataSize());
        end;
        self.methodsOffset := offset;
    end;

    procedure JavaStaticRecompiler.reduceCodeSize();
    var
        f: boolean;
        i: int;
        j: int;
        offset: int;
        classesCount: int;
        classes: JavaClass_Array1d;
        cls: JavaClass;
        method: JavaMethod;
        entryCommands: long_Array1d;
        strings: StringPool;
    begin
        classesCount := self.classesCount;
        classes := self.classes;
        repeat
            f := false;
            { сокращаем размеры команд перехода }
            for i := 0 to classesCount - 1 do begin
                cls := classes[i];
                for j := 0 to cls.getMethodsCount() - 1 do begin
                    if cls.getMethod(j).reduceSizeofJumps() then begin
                        f := true;
                    end;
                end;
            end;
            { определяем смещения методов }
            offset := methodsOffset;
            for i := 0 to classesCount - 1 do begin
                cls := classes[i];
                for j := 0 to cls.getMethodsCount() - 1 do begin
                    method := cls.getMethod(j);
                    method.setOffset(offset);
                    inc(offset, method.getCodeSize());
                end;
            end;
            { сокращаем размеры команд вызова }
            for i := 0 to classesCount - 1 do begin
                cls := classes[i];
                for j := 0 to cls.getMethodsCount() - 1 do begin
                    if cls.getMethod(j).reduceSizeofCalls() then begin
                        f := true;
                    end;
                end;
            end;
        until not f;
        { сокращаем размеры команд точки входа }
        entryOffset := offset;
        entryCommands := self.entryCommands;
        i := length(entryCommands);
        reduceSizeofCalls(entryCommands, i, offset, self);
        inc(offset, computeCodeSize(entryCommands, 0, i));
        if (offset and $03) <> 0 then begin
            offset := (offset and (-$04)) + $04;
        end;
        { определяем смещение строк }
        stringsOffset := offset;
        inc(offset, 4);
        strings := self.strings;
        for i := 0 to strings.getLength() - 1 do begin
            inc(offset, length(strings.getString(i)) + 20);
            if (offset and $03) <> 0 then begin
                offset := (offset and (-$04)) + $04;
            end;
        end;
        { определяем смещение конца образа }
        self.imageEndOffset := offset;
    end;

    procedure JavaStaticRecompiler.computeAvailableMemory();
    var
        heapEndOffset: int;
    begin
        heapEndOffset := imageEndOffset + descSize + heapSize;
        if (heapEndOffset and (-$100000)) <> 0 then begin
            heapEndOffset := (heapEndOffset and (-$100000)) + $100000;
        end;
        availableMemoryInMB := min(256, (heapEndOffset - MalikProcessor.MEMORY_START) shr 20);
    end;

    procedure JavaStaticRecompiler.createExecutableFile(const fileName: AnsiString);
    var
        i: int;
        j: int;
        len: int;
        fileContents: FileOutputStream;
        contents: ByteArrayOutputStream;
        stream: DataOutput;
        fileStream: DataOutput;
        strings: StringPool;
        member: JavaClassMember;
        cls: JavaClass;
        classes: JavaClass_Array1d;
        arrays: JavaArray_Array1d;
        primitives: JavaClass_Array1d;
        image: byte_Array1d;
        s: AnsiString;
    begin
        fileContents := FileOutputStream.create(fileName);
        if fileContents.isInvalidHandle() then begin
            fileContents.free();
            raise IOException.create('Не удалось создать файл ' + fileName);
        end;
        contents := ByteArrayOutputStream.create();
        stream := DataOutputStream.create(contents);
        fileStream := DataOutputStream.create(fileContents);
        classes := self.classes;
        arrays := self.arrays;
        primitives := self.primitives;
        { записываем таблицы методов протоколов }
        for i := 0 to classesCount - 1 do begin
            classes[i].writeIMTs(stream);
        end;
        for i := 0 to arraysCount - 1 do begin
            arrays[i].writeIMTs(stream);
        end;
        { записываем статичные поля }
        for i := 0 to classesCount - 1 do begin
            cls := classes[i];
            for j := 0 to cls.getFieldsCount() - 1 do begin
                member := cls.getField(j);
                if (not member.isStatic()) or (not JavaField(member).isOutToExecutable()) then begin
                    continue;
                end;
                if MEMBER_STRING_POOL_OFFSET.equals(member) then begin
                    stream.writeIntLE(stringsOffset);
                    continue;
                end;
                if MEMBER_DESCRIPTORS_SIZE.equals(member) then begin
                    stream.writeIntLE(descSize);
                    continue;
                end;
                if MEMBER_HEAP_LIMIT.equals(member) then begin
                    stream.writeIntLE((availableMemoryInMB shl 20) + MalikProcessor.MEMORY_START);
                    continue;
                end;
                s := member.getSignature();
                if length(s) > 0 then begin
                    case s[1] of
                    PREFIX_BOOLEAN, PREFIX_CHAR, PREFIX_FLOAT, PREFIX_BYTE, PREFIX_SHORT, PREFIX_INT, PREFIX_OBJECT, PREFIX_ARRAY: begin
                        stream.writeIntLE(0);
                    end;
                    PREFIX_DOUBLE, PREFIX_LONG: begin
                        stream.writeLongLE(0);
                    end;
                    end;
                end;
            end;
        end;
        { записываем данные классов }
        for i := 0 to classesCount - 1 do begin
            classes[i].writeData(stream);
        end;
        for i := 0 to arraysCount - 1 do begin
            arrays[i].writeData(stream);
        end;
        for i := 0 to length(primitives) - 1 do begin
            primitives[i].writeData(stream);
        end;
        { записываем код методов }
        for i := 0 to classesCount - 1 do begin
            cls := classes[i];
            for j := 0 to cls.getMethodsCount() - 1 do begin
                cls.getMethod(j).writeCode(stream);
            end;
        end;
        writeCode(stream, entryCommands, length(entryCommands), entryOffset, self);
        { записываем строки }
        cls := findClassBySignature(PREFIX_ARRAY + PREFIX_BYTE);
        strings := self.strings;
        len := strings.getLength();
        stream.writeIntLE(len);
        for i := 0 to len - 1 do begin
            s := strings.getString(i);
            j := length(s);
            stream.writeIntLE(0);
            stream.writeIntLE(0);
            JavaClass.writeClassReference(stream, cls);
            stream.writeIntLE(j);
            stream.writeIntLE(0);
            stream.writeChars(s);
            while (j and $03) <> 0 do begin
                stream.writeByte(0);
                inc(j);
            end;
        end;
        { записываем исполняемый файл }
        image := contents.toByteArray();
        if compressionLevel > 0 then begin
            image := Zlib.compress(image, compressionLevel);
            fileStream.writeIntLE($014b4c4d);
        end else begin
            fileStream.writeIntLE($004b4c4d);
        end;
        fileStream.writeIntLE(imageBeginOffset);
        fileStream.writeIntLE(entryOffset);
        fileStream.writeShortLE(availableMemoryInMB);
        fileStream.writeShortLE(stackSizeInMB);
        fileStream.write(image);
        { закрываем файл }
        fileStream := nil;
    end;

    procedure JavaStaticRecompiler.createTextDebugInfoFile(const fileName: AnsiString);
    var
        i: int;
        j: int;
        k: int;
        offset: int;
        fileContents: FileOutputStream;
        stream: DataOutput;
        lnt: JavaLineNumberTable;
        intfImpl: InterfaceImplementsInfo;
        mthdImpl: InterfaceMethodImplements;
        member: JavaClassMember;
        cls: JavaClass;
        classes: JavaClass_Array1d;
        arrays: JavaArray_Array1d;
        primitives: JavaClass_Array1d;
        s: AnsiString;
        t: AnsiString;
    begin
        fileContents := FileOutputStream.create(fileName);
        if fileContents.isInvalidHandle() then begin
            fileContents.free();
            raise IOException.create('Не удалось создать файл ' + fileName);
        end;
        stream := DataOutputStream.create(fileContents);
        classes := self.classes;
        arrays := self.arrays;
        primitives := self.primitives;
        stream.writeChars(LINE_ENDING);
        { записываем нестатичные поля }
        for i := 0 to classesCount - 1 do begin
            cls := classes[i];
            s := cls.getName();
            for j := 0 to cls.getFieldsCount() - 1 do begin
                member := cls.getField(j);
                if member.isStatic() then begin
                    continue;
                end;
                stream.writeChars(localOffsetToString(member.getOffset()) + '=' + s + '.' + member.getName() + ': ' + member.getSignature() + LINE_ENDING);
            end;
        end;
        { записываем таблицы методов протоколов }
        for i := 0 to classesCount - 1 do begin
            cls := classes[i];
            if cls.isInterface() then begin
                continue;
            end;
            s := cls.getName();
            for j := 0 to cls.getInterfacesCount() - 1 do begin
                intfImpl := cls.getInterfaceImplements(j);
                t := intfImpl.getInterfaceName();
                offset := intfImpl.getIMTOffset();
                for k := 0 to intfImpl.getMethodsCount() - 1 do begin
                    mthdImpl := intfImpl.getMethodImplements(k);
                    stream.writeChars(globalOffsetToString(offset + k * IMT_ENTRY_SIZE) + '=func:  ' + s + ' : ' + t + '.' + mthdImpl.getMethodName() + mthdImpl.getMethodSignature() + LINE_ENDING);
                end;
            end;
        end;
        { записываем статичные поля }
        for i := 0 to classesCount - 1 do begin
            cls := classes[i];
            s := cls.getName();
            for j := 0 to cls.getFieldsCount() - 1 do begin
                member := cls.getField(j);
                if (not member.isStatic()) or (not JavaField(member).isOutToExecutable()) then begin
                    continue;
                end;
                stream.writeChars(globalOffsetToString(member.getOffset()) + '=var:   ' + s + '.' + member.getName() + ': ' + member.getSignature() + LINE_ENDING);
            end;
        end;
        { записываем классы }
        for i := 0 to classesCount - 1 do begin
            writeToTextDebugInfoFile(stream, classes[i]);
        end;
        for i := 0 to arraysCount - 1 do begin
            writeToTextDebugInfoFile(stream, arrays[i]);
        end;
        for i := 0 to length(primitives) - 1 do begin
            writeToTextDebugInfoFile(stream, primitives[i]);
        end;
        { записываем методы }
        for i := 0 to classesCount - 1 do begin
            cls := classes[i];
            s := cls.getName();
            t := cls.getSourceFile();
            for j := 0 to cls.getMethodsCount() - 1 do begin
                member := cls.getMethod(j);
                if JavaMethod(member).isAbstract() then begin
                    continue;
                end;
                offset := member.getOffset();
                stream.writeChars(globalOffsetToString(offset) + '=func:  ' + s + '.' + member.getName() + member.getSignature() + LINE_ENDING);
                if length(t) = 0 then begin
                    continue;
                end;
                lnt := JavaMethod(member).getLineNumberTable();
                if lnt = nil then begin
                    continue;
                end;
                for k := 0 to lnt.getLinesCount() - 1 do begin
                    stream.writeChars(globalOffsetToString(offset + JavaMethod(member).getRelativeOffset(lnt.getCodeOffsetAt(k))) + '= ' + t + '[' + toDecString(lnt.getLineNumber(k)) + ']' + LINE_ENDING);
                end;
            end;
        end;
        stream.writeChars(globalOffsetToString(entryOffset) + '=func:  Точка входа в программу' + LINE_ENDING);
        stream.writeChars(globalOffsetToString(stringsOffset) + '=func:  Строки программы' + LINE_ENDING);
        { закрываем файл }
        stream := nil;
    end;

    procedure JavaStaticRecompiler.createBinDebugInfoFile(const fileName: AnsiString);
    var
        i: int;
        j: int;
        k: int;
        len: int;
        offset: int;
        classNameIndex: int;
        methodNameIndex: int;
        sourceFileIndex: int;
        fileContents: FileOutputStream;
        stream: DataOutput;
        cls: JavaClass;
        method: JavaMethod;
        strings: StringPool;
        table: JavaLineNumberTable;
        classes: JavaClass_Array1d;

        procedure writeRecord(offset, classNameIndex, methodNameIndex, sourceFileIndex, lineNumber: int); inline;
        begin
            stream.writeInt(offset);
            stream.writeInt(classNameIndex);
            stream.writeInt(methodNameIndex);
            stream.writeInt(sourceFileIndex);
            stream.writeShort(lineNumber);
        end;

    begin
        fileContents := FileOutputStream.create(fileName);
        if fileContents.isInvalidHandle() then begin
            fileContents.free();
            raise IOException.create('Не удалось создать файл ' + fileName);
        end;
        stream := DataOutputStream.create(fileContents);
        strings := StringPool.create();
        try
            { формируем строки }
            strings.acquireStringIndex('');
            classes := self.classes;
            for i := 0 to classesCount - 1 do begin
                cls := classes[i];
                for j := 0 to cls.getMethodsCount() - 1 do begin
                    method := cls.getMethod(j);
                    if method.isAbstract() then begin
                        continue;
                    end;
                    strings.acquireStringIndex(cls.getOutputName());
                    strings.acquireStringIndex(method.getName());
                    strings.acquireStringIndex(cls.getSourceFile());
                end;
            end;
            strings.acquireStringIndex(PROGRAMME_ENTRY);
            strings.acquireStringIndex(UNKNOWN_SOURCE);
            { записываем сформированные строки }
            len := strings.getLength();
            stream.writeInt(len);
            for i := 0 to len - 1 do begin
                stream.writeUTF(toUTF16String(strings.getString(i)));
            end;
            { записываем данные на каждый метод и каждую строку исходного кода }
            for i := 0 to classesCount - 1 do begin
                cls := classes[i];
                for j := 0 to cls.getMethodsCount() - 1 do begin
                    method := cls.getMethod(j);
                    if method.isAbstract() then begin
                        continue;
                    end;
                    offset := method.getOffset();
                    classNameIndex := strings.indexOfString(cls.getOutputName());
                    methodNameIndex := strings.indexOfString(method.getName());
                    sourceFileIndex := strings.indexOfString(cls.getSourceFile());
                    writeRecord(offset, classNameIndex, methodNameIndex, sourceFileIndex, 0);
                    table := method.getLineNumberTable();
                    if table = nil then begin
                        continue;
                    end;
                    for k := 0 to table.getLinesCount() - 1 do begin
                        writeRecord(offset + method.getRelativeOffset(table.getCodeOffsetAt(k)), classNameIndex, methodNameIndex, sourceFileIndex, table.getLineNumber(k));
                    end;
                end;
            end;
            { записываем данные на точку входа }
            classNameIndex := strings.indexOfString('');
            methodNameIndex := strings.indexOfString(PROGRAMME_ENTRY);
            sourceFileIndex := strings.indexOfString(UNKNOWN_SOURCE);
            writeRecord(entryOffset, classNameIndex, methodNameIndex, sourceFileIndex, 0);
        finally
            strings.free();
        end;
        { закрываем файл }
        stream := nil;
    end;

    procedure JavaStaticRecompiler.createStringPoolFile(const fileName: AnsiString);
    var
        i: int;
        fileContents: FileOutputStream;
        stream: DataOutput;
        strings: StringPool;
    begin
        fileContents := FileOutputStream.create(fileName);
        if fileContents.isInvalidHandle() then begin
            fileContents.free();
            raise IOException.create('Не удалось создать файл ' + fileName);
        end;
        stream := DataOutputStream.create(fileContents);
        { записываем строки программы в отдельный текстовый файл }
        strings := self.strings;
        stream.writeChars(#$ef#$bb#$bf);
        for i := 0 to strings.getLength() - 1 do begin
            stream.writeChars('[' + toDecString(i) + ']=' + strings.getString(i) + LINE_ENDING);
        end;
        { закрываем файл }
        stream := nil;
    end;

    procedure JavaStaticRecompiler.buildMalikManifest(const outputDirectory: AnsiString; javaManifest, malikManifest: ProgrammeManifest);
    var
        i: int;
        f: AnsiString;
        s: AnsiString;
        imageFileStream: FileInputStream;
        imageHandleStream: TStream;
        image: TPortableNetworkGraphic;
        iconFileStream: FileOutputStream;
        iconHandleStream: TStream;
        icon: TIcon;
    begin
        { Извлекаем значок программы (если он есть) }
        f := copy(executableFileName, 1, length(executableFileName));
        if DIRECTORY_SEPARATOR <> '/' then begin
            for i := length(f) downto 1 do begin
                if f[i] = '/' then begin
                    f[i] := DIRECTORY_SEPARATOR;
                end;
            end;
        end;
        f := outputDirectory + f + '.ico';
        s := javaManifest.getValue(MANIFEST_PROPERTY_MIDLET_ICON);
        if length(s) > 0 then begin
            if startsWith('/', s) then begin
                s := 'res' + s;
            end else begin
                s := 'res/' + s;
            end;
            if DIRECTORY_SEPARATOR <> '/' then begin
                for i := length(s) downto 1 do begin
                    if s[i] = '/' then begin
                        s[i] := DIRECTORY_SEPARATOR;
                    end;
                end;
            end;
            imageFileStream := FileInputStream.create(outputDirectory + s);
            try
                if not imageFileStream.isInvalidHandle() then begin
                    imageHandleStream := THandleStream.create(imageFileStream.getHandle());
                    try
                        image := TPortableNetworkGraphic.create();
                        try
                            image.loadFromStream(imageHandleStream);
                            icon := TIcon.create();
                            try
                                icon.assign(image);
                                iconFileStream := FileOutputStream.create(f);
                                try
                                    if not iconFileStream.isInvalidHandle() then begin
                                        iconHandleStream := THandleStream.create(iconFileStream.getHandle());
                                        try
                                            icon.saveToStream(iconHandleStream);
                                        finally
                                            iconHandleStream.free();
                                        end;
                                    end;
                                finally
                                    iconFileStream.free();
                                end;
                            finally
                                icon.free();
                            end;
                        finally
                            image.free();
                        end;
                    finally
                        imageHandleStream.free();
                    end;
                end;
            finally
                imageFileStream.free();
            end;
        end;
        { Записываем свойства }
        s := executableFileName;
        if not startsWith('/', s) then begin
            s := '/' + s;
        end;
        malikManifest.setValue(MANIFEST_PROPERTY_PROGRAMME_NAME, javaManifest.getValue(MANIFEST_PROPERTY_MIDLET_NAME));
        malikManifest.setValue(MANIFEST_PROPERTY_PROGRAMME_VERSION, javaManifest.getValue(MANIFEST_PROPERTY_MIDLET_VERSION));
        malikManifest.setValue(MANIFEST_PROPERTY_PROGRAMME_EXECUTABLE, s);
        malikManifest.setValue(MANIFEST_PROPERTY_PROGRAMME_VENDOR, javaManifest.getValue(MANIFEST_PROPERTY_MIDLET_VENDOR));
        malikManifest.setValue(MANIFEST_PROPERTY_PROGRAMME_DESCRIPTION, javaManifest.getValue(MANIFEST_PROPERTY_MIDLET_DESCRIPTION));
        s := javaManifest.getValue(MANIFEST_PROPERTY_MIDLET_SITE);
        if length(s) = 0 then begin
            for i := length(MANIFEST_PROPERTY_MIDLET_SITE_ATLERNATIVES) - 1 downto 0 do begin
                s := javaManifest.getValue(MANIFEST_PROPERTY_MIDLET_SITE_ATLERNATIVES[i]);
                if length(s) > 0 then begin
                    malikManifest.setValue(MANIFEST_PROPERTY_PROGRAMME_SITE, s);
                    break;
                end;
            end;
        end else begin
            malikManifest.setValue(MANIFEST_PROPERTY_PROGRAMME_SITE, s);
        end;
        malikManifest.setValue(MANIFEST_PROPERTY_PROGRAMME_INSTALL_NOTIFY, javaManifest.getValue(MANIFEST_PROPERTY_MIDLET_INSTALL_NOTIFY));
        malikManifest.setValue(MANIFEST_PROPERTY_PROGRAMME_DELETE_CONFIRM, javaManifest.getValue(MANIFEST_PROPERTY_MIDLET_DELETE_CONFIRM));
        malikManifest.setValue(MANIFEST_PROPERTY_PROGRAMME_DELETE_NOTIFY, javaManifest.getValue(MANIFEST_PROPERTY_MIDLET_DELETE_NOTIFY));
        malikManifest.setValue(MANIFEST_PROPERTY_SOURCE_PLATFORM, getPlatformName());
    end;

    procedure JavaStaticRecompiler.clear();
    var
        i: int;
        c: JavaClass_Array1d;
        a: JavaArray_Array1d;
    begin
        c := classes;
        for i := classesCount - 1 downto 0 do begin
            c[i].free();
        end;
        classes := nil;
        classesCount := 0;
        a := arrays;
        for i := arraysCount - 1 downto 0 do begin
            a[i].free();
        end;
        arrays := nil;
        arraysCount := 0;
        entryCommands := nil;
        initOrder.clear();
        strings.clear();
    end;

    function JavaStaticRecompiler.isJarArchiveForEmulator(archive: ZIPArchiveReader): boolean;
    var
        manifestInput: Input;
        manifest: ProgrammeManifest;
    begin
        manifestInput := archive.openFile(MANIFEST_FILE_NAME);
        if manifestInput <> nil then begin
            manifest := ProgrammeManifest.create();
            try
                manifest.loadFromStream(manifestInput);
                result := manifest.getValue('Library-Purpose') = 'Malik Emulator';
            finally
                manifest.free();
            end;
        end else begin
            result := false;
        end;
    end;

    function JavaStaticRecompiler.indexOfArray(const name: AnsiString): int;
    var
        i: int;
        arrays: JavaArray_Array1d;
    begin
        arrays := self.arrays;
        for i := arraysCount - 1 downto 0 do begin
            if arrays[i].getName() = name then begin
                result := i;
                exit;
            end;
        end;
        result := -1;
    end;

    function JavaStaticRecompiler.getArray(const name: AnsiString): JavaArray;
    var
        i: int;
        dif: int;
        len: int;
        dimcount: int;
        cellClass: JavaClass;
        nearArray: JavaClass;
        currentArray: JavaArray;
        arrays: JavaArray_Array1d;
    begin
        { если не указывается тип массива и список массивов не пуст, то возвращаем первый массив }
        if (name = PREFIX_ARRAY) and (arraysCount > 0) then begin
            result := self.arrays[0];
            exit;
        end;
        { ищем среди имеющихся массивов }
        i := indexOfArray(name);
        if i >= 0 then begin
            result := self.arrays[i];
            exit;
        end;
        { определяем количество измерений (оно равно количеству квадратных скобок в начале сигнатуры) }
        len := length(name);
        dimcount := 1;
        for i := 2 to len do begin
            if name[i] <> PREFIX_ARRAY then begin
                break;
            end;
            inc(dimcount);
        end;
        { определяем класс ячеек массива }
        cellClass := findClassBySignature(copy(name, dimcount + 1, len - dimcount));
        { ищем массив с наибольшим количеством измерений и с таким же классом ячеек }
        nearArray := cellClass;
        arrays := self.arrays;
        for i := arraysCount - 1 downto 0 do begin
            currentArray := arrays[i];
            if currentArray.getCellClass() = cellClass then begin
                nearArray := currentArray;
                break;
            end;
        end;
        { определяем разность между требуемым количеством измерений и имеющимся }
        if nearArray is JavaArray then begin
            dif := dimcount - JavaArray(nearArray).getDimensionsCount();
        end else begin
            dif := dimcount;
        end;
        { создаём массивы с недостающим количеством измерений }
        for i := 1 to dif do begin
            nearArray := JavaArray.create(self, nearArray);
            nearArray.findAncestors();
            nearArray.defineImplementedMethods();
            nearArray.defineVirtualMethods();
            nearArray.defineFieldOffsets();
            addArray(JavaArray(nearArray));
        end;
        { возвращаем последний созданный массив }
        result := nearArray as JavaArray;
    end;

    function JavaStaticRecompiler.findSystemMember(desriptor: _Object): JavaClassMember;
    var
        d: ClassMemberDescriptor;
        className: AnsiString;
        memberName: AnsiString;
        signature: AnsiString;
    begin
        d := ClassMemberDescriptor(desriptor);
        className := d.getClassName();
        memberName := d.getMemberName();
        signature := d.getSignature();
        result := findClassByName(className).findMember(memberName, signature);
        if result = nil then begin
            if (length(signature) = 0) or (signature[1] <> HEADER_PREFIX) then begin
                signature := ': ' + signature;
            end;
            raise ClassMemberNotFoundException.create('Не удалось найти системный член класса: ' + className + '.' + memberName + signature);
        end;
    end;

    procedure JavaStaticRecompiler.writeCode(stream: DataOutput; const commands: long_Array1d; commandsCount: int; offset: int; owner: ConstantPoolContainer);
    var
        codesize: int;
        cmdsize: int;
        opcode: int;
        i: int;
        c: long;
        member: JavaClassMember;
    begin
        codesize := 0;
        for i := 0 to commandsCount - 1 do begin
            c := commands[i];
            cmdsize := (int(c) shr 12) and $0f;
            opcode := int(c) and $0fff;
            inc(codesize, cmdsize);
            case opcode of
            { основные опкоды }
            $0000, $0010, $0012, $0014, $0016, $0018..$001f, $002f, $003f, $0050..$0073, $0077, $007b..$00af, $00b8..$00bc, $00be, $00cf, $00df, $00f0..$00f8, $00fc..$00fe: begin
                stream.writeByte(opcode);
            end;
            $0001, $0009, $0074..$0076, $0078..$007a, $00bd, $00bf, $00fa: begin
                stream.writeByte(opcode);
                stream.writeByte(int(c) shr 16);
            end;
            $0002, $000a, $000c, $0011, $0013, $0015, $0040, $0042..$0048, $004a..$004f, $00e0, $00e2..$00e8, $00ea..$00ef, $00f9, $00fb: begin
                stream.writeByte(opcode);
                stream.writeShortLE(int(c) shr 16);
            end;
            $0003, $000b, $0017, $0041, $0049, $00e1, $00e9: begin
                stream.writeByte(opcode);
                stream.writeIntLE(int(c shr 16));
            end;
            $0004: begin
                stream.writeByte(opcode);
                stream.writeLongLE((owner.getConstantPoolEntry(int(c shr 16)) as LongEntry).longValue());
            end;
            $0005, $000d..$000e: begin
                stream.writeByte(opcode);
                stream.writeFloatLE((owner.getConstantPoolEntry(int(c shr 16)) as FloatEntry).floatValue());
            end;
            $0006, $000f: begin
                stream.writeByte(opcode);
                stream.writeDoubleLE((owner.getConstantPoolEntry(int(c shr 16)) as DoubleEntry).doubleValue());
            end;
            $0008, $00b2: begin
                stream.writeByte(opcode);
                stream.writeIntLE(computeCodeSize(commands, i + 1, int(c shr 16)));
            end;
            $0020..$002e, $0030..$003e: begin
                member := owner.getConstantPoolMember((int(c) shr 16) and $ffff, int(c shr 32) and $ffff, int(c shr 48) and $ffff, 0);
                stream.writeByte(opcode);
                stream.writeIntLE(member.getOffset());
            end;
            $00b0: begin
                stream.writeByte(opcode);
                stream.writeByte(computeCodeSize(commands, i + 1, int(c shr 16)));
            end;
            $00b1: begin
                stream.writeByte(opcode);
                stream.writeShortLE(computeCodeSize(commands, i + 1, int(c shr 16)));
            end;
            $00b4: begin
                member := owner.getConstantPoolMember((int(c) shr 16) and $ffff, int(c shr 32) and $ffff, int(c shr 48) and $ffff, 0);
                stream.writeByte(opcode);
                stream.writeByte(member.getOffset() - (offset + codesize));
            end;
            $00b5: begin
                member := owner.getConstantPoolMember((int(c) shr 16) and $ffff, int(c shr 32) and $ffff, int(c shr 48) and $ffff, 0);
                stream.writeByte(opcode);
                stream.writeShortLE(member.getOffset() - (offset + codesize));
            end;
            $00b6: begin
                member := owner.getConstantPoolMember((int(c) shr 16) and $ffff, int(c shr 32) and $ffff, int(c shr 48) and $ffff, 0);
                stream.writeByte(opcode);
                stream.writeIntLE(member.getOffset() - (offset + codesize));
            end;
            $00c0..$00ce, $00d0..$00de: begin
                stream.writeIntLE(opcode + (int(c shr 16) shl 8));
            end;
            $0100..$011f, $0156..$0157, $0160..$017f, $01b0..$01bf, $01f2..$01f3, $01f6..$01f7, $01fa..$01fb, $01fe: begin
                stream.writeShort($fe00 + opcode);
            end;
            $0120..$012f: begin
                stream.writeShort($fe00 + opcode);
                stream.writeByte(computeCodeSize(commands, i + 1, int(c shr 16)));
            end;
            $0130..$013f: begin
                stream.writeShort($fe00 + opcode);
                stream.writeShortLE(computeCodeSize(commands, i + 1, int(c shr 16)));
            end;
            $0140..$014f: begin
                stream.writeShort($fe00 + opcode);
                stream.writeIntLE(computeCodeSize(commands, i + 1, int(c shr 16)));
            end;
            $0150..$0155: begin
                stream.writeShort($fe00 + opcode);
                stream.writeByte(int(c) shr 16);
            end;
            $015a: begin
                stream.writeShort($fe00 + opcode);
                stream.writeIntLE(int(c shr 16));
            end;
            $015e: begin
                stream.writeShort($fe00 + opcode);
                stream.writeShortLE(int(c) shr 16);
            end;
            $0180..$01a3, $01a7, $01ab..$01af, $01c0..$01e3, $01e7, $01eb..$01f1, $01f4..$01f5, $01f8..$01f9, $01fc..$01fd: begin
                stream.writeShort($fe00 + opcode);
                stream.writeShortLE(int(c) shr 16);
            end;
            $01a4..$01a6, $01a8..$01aa, $01e4..$01e6, $01e8..$01ea: begin
                stream.writeShort($fe00 + opcode);
                stream.writeShortLE(int(c) shr 16);
                stream.writeByte(int(c shr 32));
            end;
            $0200..$022f, $0233..$02ff: begin
                stream.writeShort($ffff);
                stream.writeByte(opcode);
            end;
            $0230..$0231: begin
                stream.writeShort($ffff);
                stream.writeByte(opcode);
                stream.writeShortLE(int(c) shr 16);
            end;
            $0232: begin
                stream.writeShort($ffff);
                stream.writeByte($32);
                stream.writeByte(computeCodeSize(commands, i + 1, int(c shr 16)));
            end;
            { специальные опкоды }
            $0f01..$0f02, $0f04..$0f05, $0f07..$0f0c, $0f0f..$0f10: begin
                case opcode of
                $0f01:
                    member := methodGetString;
                $0f02:
                    member := methodCheckObjectArrayAssignable;
                $0f04:
                    member := methodGetInterfaceMethodEntryPoint;
                $0f05:
                    member := methodAllocateInstance;
                $0f07:
                    member := methodArrayCreateOneDim;
                $0f08:
                    member := methodArrayCreateMultiDim;
                $0f09:
                    member := methodCast;
                $0f0a:
                    member := methodIsInstance;
                $0f0b:
                    member := methodMonitorEnter;
                $0f0c:
                    member := methodMonitorExit;
                $0f0f:
                    member := methodMain;
                $0f10:
                    member := findClassByName(initOrder.getString(int(c shr 16))).findMemberInternal(NAME_INITIALIZATION, SIGNATURE_NO_PARAMETERS);
                else
                    member := nil;
                end;
                case cmdsize of
                 2: begin
                    stream.writeByte($b4);
                    stream.writeByte(member.getOffset() - (offset + codesize));
                end;
                 3: begin
                    stream.writeByte($b5);
                    stream.writeShortLE(member.getOffset() - (offset + codesize));
                end;
                 5: begin
                    stream.writeByte($b6);
                    stream.writeIntLE(member.getOffset() - (offset + codesize));
                end;
                end;
            end;
            $0f03: begin
                stream.writeByte($0b);
                stream.writeIntLE(findClassByName((owner.getConstantPoolEntry(int(c shr 16)) as StringEntry).stringValue()).getOffset());
            end;
            $0f06: begin
                stream.writeByte($0b);
                stream.writeIntLE(findClassBySignature((owner.getConstantPoolEntry(int(c shr 16)) as StringEntry).stringValue()).getOffset());
            end;
            $0f0d: begin
                member := owner.getConstantPoolMember((int(c) shr 16) and $ffff, int(c shr 32) and $ffff, int(c shr 48) and $ffff, 0);
                stream.writeByte($0b);
                stream.writeIntLE(member.getOffset());
            end;
            $0ffd: begin
                stream.writeByte($0b);
                stream.writeIntLE(offset + computeCodeSize(commands, 0, int(c shr 16)));
            end;
            $0ffe: begin
                stream.writeIntLE(computeCodeSize(commands, i + 1, int(c shr 32)) - computeCodeSize(commands, i + 1, int(c) shr 16));
            end;
            $0fff: begin
                stream.writeIntLE(int(c shr 16));
            end;
            end;
        end;
        while (codesize and $03) <> 0 do begin
            stream.writeByte(0);
            inc(codesize);
        end;
    end;

    function JavaStaticRecompiler.reduceSizeofJumps(const commands: long_Array1d; commandsCount: int): boolean;
    var
        opcode: int;
        i: int;
        j: int;
        s: int;
        c: long;
    begin
        result := false;
        for i := 0 to commandsCount - 1 do begin
            c := commands[i];
            opcode := int(c) and $0fff;
            case opcode of
            $00b0..$00b2: begin
                j := int(c shr 16);
                commands[i] := long($20b0) + (long(j) shl 16);
                s := computeCodeSize(commands, i + 1, j);
                if (s >= -$80) and (s < $80) then begin
                    result := result or (opcode <> $00b0);
                    continue;
                end;
                commands[i] := long($30b1) + (long(j) shl 16);
                s := computeCodeSize(commands, i + 1, j);
                if (s >= -$8000) and (s < $8000) then begin
                    result := result or (opcode <> $00b1);
                    continue;
                end;
                commands[i] := long($50b2) + (long(j) shl 16);
                result := result or (opcode <> $00b2);
            end;
            $0120..$014f: begin
                j := int(c shr 16);
                commands[i] := long($3120 + long(opcode and $0f)) + (long(j) shl 16);
                s := computeCodeSize(commands, i + 1, j);
                if (s >= -$80) and (s < $80) then begin
                    result := result or ((opcode and $0ff0) <> $0120);
                    continue;
                end;
                commands[i] := long($4130 + long(opcode and $0f)) + (long(j) shl 16);
                s := computeCodeSize(commands, i + 1, j);
                if (s >= -$8000) and (s < $8000) then begin
                    result := result or ((opcode and $0ff0) <> $0130);
                    continue;
                end;
                commands[i] := long($6140 + long(opcode and $0f)) + (long(j) shl 16);
                result := result or ((opcode and $0ff0) <> $0140);
            end;
            end;
        end;
    end;

    function JavaStaticRecompiler.reduceSizeofCalls(const commands: long_Array1d; commandsCount: int; offset: int; owner: ConstantPoolContainer): boolean;
    var
        opcode: int;
        size: int;
        i: int;
        s: int;
        c: long;
        f: JavaClassMember;

        procedure setSize(newSize: int); inline;
        begin
            if (opcode >= $00b4) and (opcode <= $00b6) then begin
                case newSize of
                 2: begin
                    commands[i] := long($20b4) + (c and (-$10000));
                end;
                 3: begin
                    commands[i] := long($30b5) + (c and (-$10000));
                end;
                 5: begin
                    commands[i] := long($50b6) + (c and (-$10000));
                end;
                end;
            end else begin
                commands[i] := (c and (-$f001)) + long(newSize shl 12);
            end;
        end;

    begin
        result := false;
        for i := 0 to commandsCount - 1 do begin
            c := commands[i];
            opcode := int(c) and $0fff;
            case opcode of
            $00b4..$00b6: begin
                f := owner.getConstantPoolMember((int(c) shr 16) and $ffff, int(c shr 32) and $ffff, int(c shr 48) and $ffff, 0);
            end;
            $0f01: begin
                f := methodGetString;
            end;
            $0f02: begin
                f := methodCheckObjectArrayAssignable;
            end;
            $0f04: begin
                f := methodGetInterfaceMethodEntryPoint;
            end;
            $0f05: begin
                f := methodAllocateInstance;
            end;
            $0f07: begin
                f := methodArrayCreateOneDim;
            end;
            $0f08: begin
                f := methodArrayCreateMultiDim;
            end;
            $0f09: begin
                f := methodCast;
            end;
            $0f0a: begin
                f := methodIsInstance;
            end;
            $0f0b: begin
                f := methodMonitorEnter;
            end;
            $0f0c: begin
                f := methodMonitorExit;
            end;
            $0f0f: begin
                f := methodMain;
            end;
            $0f10: begin
                f := findClassByName(initOrder.getString(int(c shr 16))).findMemberInternal(NAME_INITIALIZATION, SIGNATURE_NO_PARAMETERS);
            end;
            else
                continue;
            end;
            size := (int(c) shr 12) and $0f;
            setSize(2);
            s := f.getOffset() - (offset + computeCodeSize(commands, 0, i + 1));
            if (s >= -$80) and (s < $80) then begin
                result := result or (size <> 2);
                continue;
            end;
            setSize(3);
            s := f.getOffset() - (offset + computeCodeSize(commands, 0, i + 1));
            if (s >= -$8000) and (s < $8000) then begin
                result := result or (size <> 3);
                continue;
            end;
            setSize(5);
            result := result or (size <> 5);
        end;
    end;

    function JavaStaticRecompiler.getStringPoolStringIndex(const str: AnsiString): int;
    begin
        result := strings.acquireStringIndex(str);
    end;

    function JavaStaticRecompiler.getClassesCount(): int;
    begin
        result := classesCount;
    end;

    function JavaStaticRecompiler.getClass(index: int): JavaClass;
    begin
        if (index < 0) or (index >= classesCount) then begin
            raise ArrayIndexOutOfBoundsException.create(index);
        end;
        result := classes[index];
    end;

    function JavaStaticRecompiler.getTypeSize(const signature: AnsiString): int;
    begin
        result := getTypeSize(signature, alignment);
    end;

    function JavaStaticRecompiler.getTypeSize(const signature: AnsiString; alignment: boolean): int;
    begin
        if length(signature) > 0 then begin
            case signature[1] of
            PREFIX_BOOLEAN, PREFIX_BYTE: begin
                if alignment then begin
                    result := 4;
                end else begin
                    result := 1;
                end;
            end;
            PREFIX_CHAR, PREFIX_SHORT: begin
                if alignment then begin
                    result := 4;
                end else begin
                    result := 2;
                end;
            end;
            PREFIX_OBJECT, PREFIX_ARRAY, PREFIX_FLOAT, PREFIX_INT: begin
                result := 4;
            end;
            PREFIX_DOUBLE, PREFIX_LONG: begin
                result := 8;
            end;
            else
                result := 0;
            end;
        end else begin
            result := 0;
        end;
    end;

    function JavaStaticRecompiler.findClassByName(const name: AnsiString): JavaClass;
    var
        i: int;
        cls: JavaClass;
        classes: JavaClass_Array1d;
    begin
        classes := self.primitives;
        for i := length(classes) - 1 downto 0 do begin
            cls := classes[i];
            if cls.getName() = name then begin
                result := cls;
                exit;
            end;
        end;
        classes := self.classes;
        for i := classesCount - 1 downto 0 do begin
            cls := classes[i];
            if cls.getName() = name then begin
                result := cls;
                exit;
            end;
        end;
        if (length(name) = 0) or (name[1] <> PREFIX_ARRAY) then begin
            raise ClassNotFoundException.create('Класс не найден: ' + name);
        end;
        result := getArray(name);
    end;

    function JavaStaticRecompiler.findClassBySignature(const signature: AnsiString): JavaClass;
    var
        i: int;
        cls: JavaClass;
        classes: JavaClass_Array1d;
    begin
        classes := self.primitives;
        for i := length(classes) - 1 downto 0 do begin
            cls := classes[i];
            if cls.getSignature() = signature then begin
                result := cls;
                exit;
            end;
        end;
        classes := self.classes;
        for i := classesCount - 1 downto 0 do begin
            cls := classes[i];
            if cls.getSignature() = signature then begin
                result := cls;
                exit;
            end;
        end;
        if (length(signature) = 0) or (signature[1] <> PREFIX_ARRAY) then begin
            raise ClassNotFoundException.create('Класс не найден: ' + signature);
        end;
        result := getArray(signature);
    end;

    function JavaStaticRecompiler.createInstance(): StaticRecompiler;
    var
        r: JavaStaticRecompiler;
    begin
        r := JavaStaticRecompiler.create(coreDirectory, platformName);
        r.outStrings := outStrings;
        r.alignment := alignment;
        r.compressionLevel := compressionLevel;
        r.stackSizeInMB := stackSizeInMB;
        r.heapSize := heapSize;
        r.descSize := descSize;
        r.executableFileName := executableFileName;
        result := r;
    end;

    function JavaStaticRecompiler.install(const archive, destinationDirectory: AnsiString): AnsiString;
    var
        i: int;
        len: int;
        s: AnsiString;
        subDirectory: AnsiString;
        outputDirectory: AnsiString;
        fileName: AnsiString;
        files: UnicodeString_Array1d;
        manifestInput: Input;
        malikManifest: ProgrammeManifest;
        javaManifest: ProgrammeManifest;
        jarArchive: ZIPArchiveReader;
        coreArchive: ZIPArchiveReader;
        inFile: Input;
        outFile: Output;
    begin
        {
            1. Выполнить статическую рекомпиляцию
            2. Определить папку, куда будем устанавливать программу (назовём её условно <outputDirectory>)
            3. Записать исполянемый файл и файлы с отладочной информацией
            4. Скопировать файлы из папки <emulator>\java\programme\ в папку <outputDirectory>
            5. Извлечь файлы (кроме файлов .class) из архива в папку <outputDirectory>\res
            6. Записать файл манифеста
            7. Очистить память
        }
        jarArchive := ZIPArchiveReader.create(FileInputStream.create(archive));
        try
            manifestInput := jarArchive.openFile(MANIFEST_FILE_NAME);
            if manifestInput = nil then begin
                raise ManifestNotFoundException.create('В архиве не найден файл манифеста (' + MANIFEST_FILE_NAME + ').');
            end;
            javaManifest := ProgrammeManifest.create();
            try
                { 1. Выполнить статическую рекомпиляцию }
                with initOrder do begin
                    acquireStringIndex(CLASS_MEMORY);
                    acquireStringIndex(CLASS_CLASS);
                    acquireStringIndex(CLASS_THREAD);
                    acquireStringIndex(CLASS_THROWABLE);
                    acquireStringIndex(CLASS_STRING);
                    acquireStringIndex(CLASS_STRING_POOL);
                    acquireStringIndex(CLASS_RUNTIME);
                    acquireStringIndex(CLASS_SYSTEM);
                end;
                files := enumerateFiles(coreDirectory, 'jar');
                for i := 0 to length(files) - 1 do begin
                    coreArchive := ZIPArchiveReader.create(FileInputStream.create(files[i]));
                    try
                        if isJarArchiveForEmulator(coreArchive) then begin
                            loadClassesFrom(coreArchive);
                        end;
                    finally
                        coreArchive.free();
                    end;
                end;
                loadClassesFrom(jarArchive);
                findAncestors();
                findSystemMembers();
                sortClasses();
                preferenceForClassMembers();
                prepareMethods();
                prepareEntry();
                linkClasses();
                extractStrings();
                assignOffsets();
                reduceCodeSize();
                computeAvailableMemory();
                { 2. Определить папку, куда будем устанавливать программу (назовём её условно outputDirectory) }
                javaManifest.loadFromStream(manifestInput);
                s := trim(javaManifest.getValue(MANIFEST_PROPERTY_MIDLET_NAME));
                i := length(s);
                s := copy(s, 1, i);
                while i > 0 do begin
                    if s[i] in ['\', '/', ':', '*', '?', '"', '<', '>', '|'] then begin
                        delete(s, i, 1);
                    end;
                    dec(i);
                end;
                if length(s) = 0 then begin
                    raise ManifestPropertyNotFoundException.create('В манифесте нет свойства ' + MANIFEST_PROPERTY_MIDLET_NAME + 'или оно содержит только недопустимые символы или не имеет символов.');
                end;
                result := destinationDirectory + s;
                if fileExists(result) then begin
                    for i := 1 to MAX_INT do begin
                        s := result + ' (' + toDecString(i) + ')';
                        if fileExists(s) then continue;
                        result := s;
                        break;
                    end;
                end;
                if not createDirectory(result) then begin
                    raise IOException.create('Не удалось создать папку программы ' + result);
                end;
                outputDirectory := result + DIRECTORY_SEPARATOR;
                { 3. Записать исполянемый файл и файлы с отладочной информацией }
                s := outputDirectory + executableFileName;
                for i := 1 to length(s) do begin
                    if s[i] = '/' then begin
                        s[i] := DIRECTORY_SEPARATOR;
                    end;
                end;
                createExecutableFile(s + '.mal');
                createTextDebugInfoFile(s + '.dbg');
                createBinDebugInfoFile(s + '.bindbg');
                if outStrings then begin
                    createStringPoolFile(s + '.txt');
                end;
                { 4. Скопировать файлы из папки <emulator>\java\programme\ в папку <outputDirectory> }
                copyFiles(coreDirectory + 'programme' + DIRECTORY_SEPARATOR, outputDirectory);
                { 5. Извлечь файлы (кроме файлов .class) из архива в папку <outputDirectory>\res }
                subDirectory := outputDirectory + 'res';
                if not createDirectory(subDirectory) then begin
                    raise IOException.create('Не удалось создать папку ' + subDirectory);
                end;
                s := outputDirectory + 'res' + DIRECTORY_SEPARATOR;
                jarArchive.gotoFirstFile();
                while not jarArchive.isStayOnEnd() do begin
                    fileName := jarArchive.getCurrentFileName();
                    if endsWith(CLASS_FILE_EXTENSION, toLowerCase(fileName)) then begin
                        jarArchive.gotoNextFile();
                        continue;
                    end;
                    len := length(fileName);
                    fileName := copy(fileName, 1, len);
                    if DIRECTORY_SEPARATOR <> '/' then begin
                        for i := len downto 1 do begin
                            if fileName[i] = '/' then begin
                                fileName[i] := DIRECTORY_SEPARATOR;
                            end;
                        end;
                    end;
                    for i := len downto 1 do begin
                        if fileName[i] = DIRECTORY_SEPARATOR then begin
                            subDirectory := s + copy(fileName, 1, i - 1);
                            if not createDirectory(subDirectory) then begin
                                raise IOException.create('Не удалось создать папку ' + subDirectory);
                            end;
                            break;
                        end;
                    end;
                    inFile := jarArchive.openCurrentFile();
                    if inFile = nil then begin
                        continue;
                    end;
                    outFile := FileOutputStream.create(s + fileName);
                    try
                        copyBytes(inFile, outFile, inFile.available());
                    finally
                        inFile := nil;
                        outFile := nil;
                    end;
                end;
                { 6. Записать файл манифеста }
                subDirectory := outputDirectory + MANIFEST_DIRECTORY;
                if not createDirectory(subDirectory) then begin
                    raise IOException.create('Не удалось создать папку ' + subDirectory);
                end;
                malikManifest := ProgrammeManifest.create();
                try
                    buildMalikManifest(outputDirectory, javaManifest, malikManifest);
                    malikManifest.saveToFile(outputDirectory + MANIFEST_FILE_PLATFORM_NAME);
                finally
                    malikManifest.free();
                end;
                { 7. Очистить память }
            finally
                javaManifest.free();
            end;
        finally
            jarArchive.free();
            clear(); { Убрать всё за собой :=) }
        end;
    end;

    function JavaStaticRecompiler.getPlatformName(): AnsiString;
    begin
        result := platformName;
    end;

    function JavaStaticRecompiler.getArchiveTypeName(): AnsiString;
    begin
        result := 'Архив программ для J2ME';
    end;

    function JavaStaticRecompiler.getArchiveExtension(): AnsiString;
    begin
        result := 'jar';
    end;

    function JavaStaticRecompiler.getAlignment(): boolean;
    begin
        result := alignment;
    end;

    function JavaStaticRecompiler.getCompressionLevel(): int;
    begin
        result := compressionLevel;
    end;

    function JavaStaticRecompiler.getStackSize(): int;
    begin
        result := stackSizeInMB;
    end;

    function JavaStaticRecompiler.getHeapSize(): int;
    begin
        result := heapSize shr 10;
    end;

    function JavaStaticRecompiler.getDescriptorsSize(): int;
    begin
        result := descSize shr 10;
    end;

    function JavaStaticRecompiler.getOutputExecutableFileName(): AnsiString;
    begin
        result := '/' + executableFileName;
    end;

    procedure JavaStaticRecompiler.setDefaults();
    begin
        alignment := true;
        compressionLevel := 0;
        stackSizeInMB := 1;
        heapSize := 20 shl 20; { 20 МБ }
        descSize := heapSize shr 5; { 1/32 от размера кучи }
        executableFileName := 'executable';
    end;

    procedure JavaStaticRecompiler.setAlignment(alignment: boolean);
    begin
        self.alignment := alignment;
    end;

    procedure JavaStaticRecompiler.setCompressionLevel(compressionLevel: int);
    begin
        self.compressionLevel := max(0, min(compressionLevel, 9));
    end;

    procedure JavaStaticRecompiler.setStackSize(sizeInMB: int);
    begin
        self.stackSizeInMB := max(1, min(sizeInMB, 16));
    end;

    procedure JavaStaticRecompiler.setHeapSize(sizeInKB: int);
    begin
        self.heapSize := max(4 shl 20, min(sizeInKB shl 10, 256 shl 20));
    end;

    procedure JavaStaticRecompiler.setDescriptorsSize(sizeInKB: int);
    begin
        self.descSize := sizeInKB shl 10;
    end;

    procedure JavaStaticRecompiler.setOutputExecutableFileName(const relativeFileName: AnsiString);
    begin
        if startsWith('/', relativeFileName) then begin
            self.executableFileName := copy(relativeFileName, 2, length(relativeFileName) - 1);
        end else begin
            self.executableFileName := relativeFileName;
        end;
    end;

    function JavaStaticRecompiler.getLongIndex(value: long): int;
    begin
        result := MIN_INT;
    end;

    function JavaStaticRecompiler.getDoubleIndex(value: double): int;
    begin
        result := MIN_INT;
    end;

    function JavaStaticRecompiler.getStringIndex(const value: AnsiString): int;
    begin
        result := MIN_INT;
    end;

    function JavaStaticRecompiler.getConstantPoolLength(): int;
    begin
        result := 0;
    end;

    function JavaStaticRecompiler.getConstantPoolEntry(index: int): ConstantPoolEntry;
    begin
        if (index < -5) or (index >= 0) then begin
            raise ArrayIndexOutOfBoundsException.create(index);
        end;
        result := fdconst[index + 5];
    end;

    function JavaStaticRecompiler.getConstantPoolMember(index, memberType: int): JavaClassMember;
    begin
        result := nil;
    end;

    function JavaStaticRecompiler.getConstantPoolMember(classNameIndex, memberNameIndex, memberSignatureIndex, memberType: int): JavaClassMember;
    begin
        result := nil;
    end;
{%endregion}

{%region ClassMemberDescriptor }
    constructor ClassMemberDescriptor.create(const className, memberName, signature: AnsiString);
    begin
        inherited create();
        self.className := className;
        self.memberName := memberName;
        self.signature := signature;
    end;

    function ClassMemberDescriptor.getClassName(): AnsiString;
    begin
        result := className;
    end;

    function ClassMemberDescriptor.getMemberName(): AnsiString;
    begin
        result := memberName;
    end;

    function ClassMemberDescriptor.getSignature(): AnsiString;
    begin
        result := signature;
    end;

    function ClassMemberDescriptor.equals(member: JavaClassMember): boolean;
    begin
        result := (className = member.getOwner().getName()) and (memberName = member.getName()) and (signature = member.getSignature());
    end;
{%endregion}

initialization {%region}
    PRIMITIVES_NAMES := toStringArray1d([
        'boolean', 'char', 'float', 'double', 'byte', 'short', 'int', 'long', 'void'
    ]);
    PRIMITIVES_PREFIXES := toStringArray1d([
        PREFIX_BOOLEAN, PREFIX_CHAR, PREFIX_FLOAT, PREFIX_DOUBLE,
        PREFIX_BYTE, PREFIX_SHORT, PREFIX_INT, PREFIX_LONG, PREFIX_VOID
    ]);
    MEMBER_TYPES := toStringArray1d([
        'статичное поле', 'поле объекта', 'статичный метод', 'метод объекта', 'метод протокола'
    ]);
    MEMBER_DEFAULT_CONSTRUCTOR := ClassMemberDescriptor.create(CLASS_OBJECT, NAME_CONSTRUCTOR, SIGNATURE_NO_PARAMETERS);
    MEMBER_STRING_POOL_OFFSET := ClassMemberDescriptor.create(CLASS_MEMORY, 'STRING_POOL_OFFSET', PREFIX_INT);
    MEMBER_DESCRIPTORS_SIZE := ClassMemberDescriptor.create(CLASS_MEMORY, 'DESCRIPTORS_SIZE', PREFIX_INT);
    MEMBER_HEAP_LIMIT := ClassMemberDescriptor.create(CLASS_MEMORY, 'HEAP_LIMIT', PREFIX_INT);
    MEMBER_MONITOR_ENTER := ClassMemberDescriptor.create(CLASS_OBJECT, 'monitorenter', SIGNATURE_NO_PARAMETERS);
    MEMBER_MONITOR_EXIT := ClassMemberDescriptor.create(CLASS_OBJECT, 'monitorexit', SIGNATURE_NO_PARAMETERS);
    MEMBER_CHECK_OBJECT_ARRAY_ASSIGNABLE := ClassMemberDescriptor.create(CLASS_OBJECT, 'checkObjectArrayAssignable',
        HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + HEADER_SUFFIX + PREFIX_VOID
    );
    MEMBER_GET_INTERFACE_METHOD_ENTRY_POINT := ClassMemberDescriptor.create(CLASS_CLASS, 'getInterfaceMethodEntryPoint',
        HEADER_PREFIX + PREFIX_OBJECT + CLASS_CLASS + SUFFIX_OBJECT + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
    );
    MEMBER_ALLOCATE_INSTANCE := ClassMemberDescriptor.create(CLASS_CLASS, 'allocateInstance',
        HEADER_PREFIX + HEADER_SUFFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT
    );
    MEMBER_IS_INSTANCE := ClassMemberDescriptor.create(CLASS_CLASS, 'isInstance',
        HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + HEADER_SUFFIX + PREFIX_BOOLEAN
    );
    MEMBER_CAST := ClassMemberDescriptor.create(CLASS_CLASS, 'cast',
        HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + HEADER_SUFFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT
    );
    MEMBER_GET_STRING := ClassMemberDescriptor.create(CLASS_STRING_POOL, 'getString',
        HEADER_PREFIX + PREFIX_INT + HEADER_SUFFIX + PREFIX_OBJECT + CLASS_STRING + SUFFIX_OBJECT
    );
    MEMBER_ARRAY_CREATE_ONE_DIM := ClassMemberDescriptor.create(CLASS_ARRAY, 'create',
        HEADER_PREFIX + PREFIX_INT + PREFIX_OBJECT + CLASS_CLASS + SUFFIX_OBJECT + HEADER_SUFFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT
    );
    MEMBER_ARRAY_CREATE_MULTI_DIM := ClassMemberDescriptor.create(CLASS_ARRAY, 'create',
        HEADER_PREFIX + PREFIX_ARRAY + PREFIX_INT + PREFIX_OBJECT + CLASS_CLASS + SUFFIX_OBJECT + HEADER_SUFFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT
    );
    MEMBER_THROW_ABSTRACT := ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'throwAbstractMethodError', SIGNATURE_NO_PARAMETERS);
    MEMBER_MAIN_PROCEDURE := ClassMemberDescriptor.create(CLASS_RUN, 'main', SIGNATURE_NO_PARAMETERS);
    NATIVE_METHODS := toClassMemberDescriptorArray1d([
        //  0
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'syscall',
            HEADER_PREFIX + PREFIX_LONG + PREFIX_INT + HEADER_SUFFIX + PREFIX_LONG
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getLocalVariableAddress',
            HEADER_PREFIX + PREFIX_BOOLEAN + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getLocalVariableAddress',
            HEADER_PREFIX + PREFIX_FLOAT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getLocalVariableAddress',
            HEADER_PREFIX + PREFIX_DOUBLE + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getLocalVariableAddress',
            HEADER_PREFIX + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        //  5
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getLocalVariableAddress',
            HEADER_PREFIX + PREFIX_LONG + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getLocalVariableAddress',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getObjectFieldAddress',
            HEADER_PREFIX + PREFIX_BOOLEAN + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getObjectFieldAddress',
            HEADER_PREFIX + PREFIX_FLOAT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getObjectFieldAddress',
            HEADER_PREFIX + PREFIX_DOUBLE + HEADER_SUFFIX + PREFIX_INT
        ),
        // 10
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getObjectFieldAddress',
            HEADER_PREFIX + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getObjectFieldAddress',
            HEADER_PREFIX + PREFIX_LONG + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getObjectFieldAddress',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'interrupt',
            HEADER_PREFIX + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'interrupt',
            HEADER_PREFIX + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        // 15
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'interrupt',
            HEADER_PREFIX + PREFIX_LONG + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'interrupt',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'runExcept',
            HEADER_PREFIX + PREFIX_INT + PREFIX_OBJECT + CLASS_THROWABLE + SUFFIX_OBJECT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getFloatAt',
            HEADER_PREFIX + PREFIX_INT + HEADER_SUFFIX + PREFIX_FLOAT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getDoubleAt',
            HEADER_PREFIX + PREFIX_INT + HEADER_SUFFIX + PREFIX_DOUBLE
        ),
        // 20
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getByteAt',
            HEADER_PREFIX + PREFIX_INT + HEADER_SUFFIX + PREFIX_BYTE
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getShortAt',
            HEADER_PREFIX + PREFIX_INT + HEADER_SUFFIX + PREFIX_SHORT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getIntAt',
            HEADER_PREFIX + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getLongAt',
            HEADER_PREFIX + PREFIX_INT + HEADER_SUFFIX + PREFIX_LONG
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getObjectAt',
            HEADER_PREFIX + PREFIX_INT + HEADER_SUFFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT
        ),
        // 25
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'setFloatAt',
            HEADER_PREFIX + PREFIX_INT + PREFIX_FLOAT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'setDoubleAt',
            HEADER_PREFIX + PREFIX_INT + PREFIX_DOUBLE + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'setByteAt',
            HEADER_PREFIX + PREFIX_INT + PREFIX_BYTE + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'setShortAt',
            HEADER_PREFIX + PREFIX_INT + PREFIX_SHORT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'setIntAt',
            HEADER_PREFIX + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        // 30
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'setLongAt',
            HEADER_PREFIX + PREFIX_INT + PREFIX_LONG + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'setObjectAt',
            HEADER_PREFIX + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getCurrentThreadID',
            HEADER_PREFIX + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getReturnAddress',
            HEADER_PREFIX + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'convertToReference',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + HEADER_SUFFIX + PREFIX_INT
        ),
        // 35
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'convertToObject',
            HEADER_PREFIX + PREFIX_INT + HEADER_SUFFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getMethodAddress',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_STRING + SUFFIX_OBJECT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getClassInstance',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_STRING + SUFFIX_OBJECT + HEADER_SUFFIX + PREFIX_OBJECT + CLASS_CLASS + SUFFIX_OBJECT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arraycopyf_byte',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arraycopyf_short',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        // 40
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arraycopyf_int',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arraycopyf_long',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arraycopyf_float',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arraycopyf_double',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arraycopyf_object',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        // 45
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arraycopyb_byte',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arraycopyb_short',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arraycopyb_int',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arraycopyb_long',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arraycopyb_float',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        // 50
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arraycopyb_double',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arraycopyb_object',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfindf_byte',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfindf_short',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfindf_int',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        // 55
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfindf_long',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_LONG + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfindf_float',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_FLOAT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfindf_double',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_DOUBLE + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfindf_object',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfindb_byte',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        // 60
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfindb_short',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfindb_int',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfindb_long',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_LONG + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfindb_float',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_FLOAT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfindb_double',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_DOUBLE + HEADER_SUFFIX + PREFIX_INT
        ),
        // 65
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfindb_object',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfill_byte',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfill_short',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfill_int',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfill_long',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + PREFIX_LONG + HEADER_SUFFIX + PREFIX_VOID
        ),
        // 70
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'arrayfill_object',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_INT + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + HEADER_SUFFIX + PREFIX_VOID
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'findfreef',
            HEADER_PREFIX + PREFIX_ARRAY + PREFIX_LONG + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'findfreeb',
            HEADER_PREFIX + PREFIX_ARRAY + PREFIX_LONG + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'findzerof',
            HEADER_PREFIX + PREFIX_ARRAY + PREFIX_LONG + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'findzerob',
            HEADER_PREFIX + PREFIX_ARRAY + PREFIX_LONG + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        // 75
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getObjectRefs',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_ARRAY + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'getArrayRefs',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + PREFIX_INT + PREFIX_ARRAY + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'blockfindf',
            HEADER_PREFIX + PREFIX_ARRAY + PREFIX_LONG + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'blockfindb',
            HEADER_PREFIX + PREFIX_ARRAY + PREFIX_LONG + PREFIX_INT + PREFIX_INT + HEADER_SUFFIX + PREFIX_INT
        ),
        ClassMemberDescriptor.create(CLASS_MALIK_SYSTEM, 'invokeDefaultConstructor',
            HEADER_PREFIX + PREFIX_OBJECT + CLASS_OBJECT + SUFFIX_OBJECT + HEADER_SUFFIX + PREFIX_VOID
        ),
        // 80
        ClassMemberDescriptor.create(CLASS_MATH, 'intPart',
            HEADER_PREFIX + PREFIX_DOUBLE + HEADER_SUFFIX + PREFIX_DOUBLE
        ),
        ClassMemberDescriptor.create(CLASS_MATH, 'fracPart',
            HEADER_PREFIX + PREFIX_DOUBLE + HEADER_SUFFIX + PREFIX_DOUBLE
        ),
        ClassMemberDescriptor.create(CLASS_MATH, 'sqrt',
            HEADER_PREFIX + PREFIX_DOUBLE + HEADER_SUFFIX + PREFIX_DOUBLE
        ),
        ClassMemberDescriptor.create(CLASS_MATH, 'arctan',
            HEADER_PREFIX + PREFIX_DOUBLE + PREFIX_DOUBLE + HEADER_SUFFIX + PREFIX_DOUBLE
        ),
        ClassMemberDescriptor.create(CLASS_MATH, 'sin',
            HEADER_PREFIX + PREFIX_DOUBLE + HEADER_SUFFIX + PREFIX_DOUBLE
        ),
        // 85
        ClassMemberDescriptor.create(CLASS_MATH, 'cos',
            HEADER_PREFIX + PREFIX_DOUBLE + HEADER_SUFFIX + PREFIX_DOUBLE
        ),
        ClassMemberDescriptor.create(CLASS_MATH, 'pow2',
            HEADER_PREFIX + PREFIX_DOUBLE + HEADER_SUFFIX + PREFIX_DOUBLE
        ),
        ClassMemberDescriptor.create(CLASS_MATH, 'log2',
            HEADER_PREFIX + PREFIX_DOUBLE + HEADER_SUFFIX + PREFIX_DOUBLE
        ),
        ClassMemberDescriptor.create(CLASS_MATH, 'floor',
            HEADER_PREFIX + PREFIX_DOUBLE + HEADER_SUFFIX + PREFIX_DOUBLE
        ),
        ClassMemberDescriptor.create(CLASS_MATH, 'ceil',
            HEADER_PREFIX + PREFIX_DOUBLE + HEADER_SUFFIX + PREFIX_DOUBLE
        ),
        // 90
        ClassMemberDescriptor.create(CLASS_MATH, 'round',
            HEADER_PREFIX + PREFIX_DOUBLE + HEADER_SUFFIX + PREFIX_LONG
        )
    ]);
    addRecompiler(JavaStaticRecompiler.create());
{%endregion}

finalization {%region}
    MEMBER_DEFAULT_CONSTRUCTOR.free();
    MEMBER_STRING_POOL_OFFSET.free();
    MEMBER_DESCRIPTORS_SIZE.free();
    MEMBER_HEAP_LIMIT.free();
    MEMBER_MONITOR_ENTER.free();
    MEMBER_MONITOR_EXIT.free();
    MEMBER_CHECK_OBJECT_ARRAY_ASSIGNABLE.free();
    MEMBER_GET_INTERFACE_METHOD_ENTRY_POINT.free();
    MEMBER_ALLOCATE_INSTANCE.free();
    MEMBER_IS_INSTANCE.free();
    MEMBER_CAST.free();
    MEMBER_GET_STRING.free();
    MEMBER_ARRAY_CREATE_ONE_DIM.free();
    MEMBER_ARRAY_CREATE_MULTI_DIM.free();
    MEMBER_THROW_ABSTRACT.free();
    MEMBER_MAIN_PROCEDURE.free();
    freeClassMemberDescriptors(NATIVE_METHODS);
{%endregion}

end.

