{
    IOStreams содержит классы для создания потоков ввода-вывода.

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

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

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

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

unit IOStreams;

{$MODE DELPHI}

interface

uses
    Lang;

{%region public }
const
    INPUT_GUID = '{74C86B60-AC5B-4E8D-8ACD-29A50B5C1503}';
    DATA_INPUT_GUID = '{74C86B60-AC5B-4E8D-8ACD-29A50B5C1504}';
    OUTPUT_GUID = '{74C86B60-AC5B-4E8D-8ACD-29A50B5C1505}';
    DATA_OUTPUT_GUID = '{74C86B60-AC5B-4E8D-8ACD-29A50B5C1506}';

type
    Input = interface;
    DataInput = interface;
    Output = interface;
    DataOutput = interface;
    IOException = class;
    EOFException = class;
    UTFDataFormatException = class;
    InputStream = class;
    OutputStream = class;
    DataInputStream = class;
    DataOutputStream = class;
    InputOutputStream = class;
    ByteArrayInputStream = class;
    ByteArrayOutputStream = class;
    ByteArrayStream = class;

    Input = interface(_Interface) [INPUT_GUID]
        function seekSupported(): boolean;
        function size(): long;
        function position(): long;
        function available(): long;
        function seek(delta: long): long;
        function read(const dst: byte_Array1d; offset, length: int): int; overload;
        function read(const dst: byte_Array1d): int; overload;
        function read(): int; overload;
    end;

    DataInput = interface(Input) [DATA_INPUT_GUID]
        function readBoolean(): boolean;
        function readByte(): int;
        function readShort(): int;
        function readShortLE(): int;
        function readInt(): int;
        function readIntLE(): int;
        function readLong(): long;
        function readLongLE(): long;
        function readFloat(): real;
        function readFloatLE(): real;
        function readDouble(): real;
        function readDoubleLE(): real;
        function readReal(): real;
        function readRealLE(): real;
        function readUnsignedByte(): int;
        function readUnsignedShort(): int;
        function readUnsignedShortLE(): int;
        function readChar(): wchar;
        function readCharLE(): wchar;
        function readUTF(): UnicodeString;
        function readExtUTF(): UnicodeString;
        procedure readFully(const dst: byte_Array1d); overload;
        procedure readFully(const dst: byte_Array1d; offset, length: int); overload;
        procedure skipBytes(count: int);
    end;

    Output = interface(_Interface) [OUTPUT_GUID]
        function write(const src: byte_Array1d; offset, length: int): int; overload;
        function write(const src: byte_Array1d): int; overload;
        function write(value: int): boolean; overload;
    end;

    DataOutput = interface(Output) [DATA_OUTPUT_GUID]
        procedure writeBoolean(a: boolean);
        procedure writeByte(a: int);
        procedure writeShort(a: int);
        procedure writeShortLE(a: int);
        procedure writeInt(a: int);
        procedure writeIntLE(a: int);
        procedure writeLong(a: long);
        procedure writeLongLE(a: long);
        procedure writeFloat(a: real);
        procedure writeFloatLE(a: real);
        procedure writeDouble(a: real);
        procedure writeDoubleLE(a: real);
        procedure writeReal(a: real);
        procedure writeRealLE(a: real);
        procedure writeChar(a: wchar);
        procedure writeCharLE(a: wchar);
        procedure writeChars(const s: AnsiString);
        procedure writeUTF(const s: UnicodeString);
        procedure writeExtUTF(const s: UnicodeString);
        procedure writeFully(const src: byte_Array1d); overload;
        procedure writeFully(const src: byte_Array1d; offset, length: int); overload;
    end;

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

    EOFException = class(IOException)
    public
        constructor create(); overload;
        constructor create(const message: AnsiString); overload;
    end;

    UTFDataFormatException = class(IOException)
    public
        constructor create(); overload;
        constructor create(const message: AnsiString); overload;
    end;

    InputStream = class(RefCountInterfacedObject, Input)
    public
        constructor create();
        function seekSupported(): boolean; virtual;
        function size(): long; virtual; abstract;
        function position(): long; virtual; abstract;
        function available(): long; virtual;
        function seek(delta: long): long; virtual;
        function read(const dst: byte_Array1d; offset, length: int): int; overload; virtual;
        function read(const dst: byte_Array1d): int; overload; virtual;
        function read(): int; overload; virtual; abstract;
    end;

    OutputStream = class(RefCountInterfacedObject, Output)
    public
        constructor create();
        function write(const src: byte_Array1d; offset, length: int): int; overload; virtual;
        function write(const src: byte_Array1d): int; overload; virtual;
        function write(value: int): boolean; overload; virtual; abstract;
    end;

    DataInputStream = class(InputStream, DataInput)
    strict private
        buffer: byte_Array1d;
    protected
        stream: Input;
    public
        constructor create(stream: Input);
        function seekSupported(): boolean; override;
        function size(): long; override;
        function position(): long; override;
        function available(): long; override;
        function seek(delta: long): long; override;
        function read(const dst: byte_Array1d; offset, length: int): int; overload; override;
        function read(const dst: byte_Array1d): int; overload; override;
        function read(): int; overload; override;
        function readBoolean(): boolean;
        function readByte(): int;
        function readShort(): int;
        function readShortLE(): int;
        function readInt(): int;
        function readIntLE(): int;
        function readLong(): long;
        function readLongLE(): long;
        function readFloat(): real;
        function readFloatLE(): real;
        function readDouble(): real;
        function readDoubleLE(): real;
        function readReal(): real;
        function readRealLE(): real;
        function readUnsignedByte(): int;
        function readUnsignedShort(): int;
        function readUnsignedShortLE(): int;
        function readChar(): wchar;
        function readCharLE(): wchar;
        function readUTF(): UnicodeString;
        function readExtUTF(): UnicodeString;
        procedure readFully(const dst: byte_Array1d); overload;
        procedure readFully(const dst: byte_Array1d; offset, length: int); overload;
        procedure skipBytes(count: int);
    end;

    DataOutputStream = class(OutputStream, DataOutput)
    protected
        stream: Output;
    public
        constructor create(stream: Output);
        function write(const src: byte_Array1d; offset, length: int): int; overload; override;
        function write(const src: byte_Array1d): int; overload; override;
        function write(value: int): boolean; overload; override;
        procedure writeBoolean(a: boolean);
        procedure writeByte(a: int);
        procedure writeShort(a: int);
        procedure writeShortLE(a: int);
        procedure writeInt(a: int);
        procedure writeIntLE(a: int);
        procedure writeLong(a: long);
        procedure writeLongLE(a: long);
        procedure writeFloat(a: real);
        procedure writeFloatLE(a: real);
        procedure writeDouble(a: real);
        procedure writeDoubleLE(a: real);
        procedure writeReal(a: real);
        procedure writeRealLE(a: real);
        procedure writeChar(a: wchar);
        procedure writeCharLE(a: wchar);
        procedure writeChars(const s: AnsiString);
        procedure writeUTF(const s: UnicodeString);
        procedure writeExtUTF(const s: UnicodeString);
        procedure writeFully(const src: byte_Array1d); overload;
        procedure writeFully(const src: byte_Array1d; offset, length: int); overload;
    end;

    InputOutputStream = class(RefCountInterfacedObject, Input, Output)
    public
        constructor create();
        { Input }
        function seekSupported(): boolean; virtual;
        function size(): long; virtual; abstract;
        function position(): long; virtual; abstract;
        function available(): long; virtual;
        function seek(delta: long): long; virtual;
        function read(const dst: byte_Array1d; offset, length: int): int; overload; virtual;
        function read(const dst: byte_Array1d): int; overload; virtual;
        function read(): int; overload; virtual; abstract;
        { Output }
        function write(const src: byte_Array1d; offset, length: int): int; overload; virtual;
        function write(const src: byte_Array1d): int; overload; virtual;
        function write(value: int): boolean; overload; virtual; abstract;
        { Собственные методы }
        function truncate(): long; virtual; abstract;
        function getDataInput(): DataInput;
        function getDataOutput(): DataOutput;
    end;

    ByteArrayInputStream = class(InputStream)
    protected
        start: int;
        count: int;
        pos: int;
        buf: byte_Array1d;
    public
        constructor create(const buf: byte_Array1d); overload;
        constructor create(const buf: byte_Array1d; offset, length: int); overload;
        function seekSupported(): boolean; override;
        function size(): long; override;
        function position(): long; override;
        function available(): long; override;
        function seek(delta: long): long; override;
        function read(const dst: byte_Array1d; offset, length: int): int; overload; override;
        function read(): int; overload; override;
    end;

    ByteArrayOutputStream = class(OutputStream)
    protected
        count: int;
        buf: byte_Array1d;
    public
        constructor create(); overload;
        function write(const src: byte_Array1d; offset, length: int): int; overload; override;
        function write(value: int): boolean; overload; override;
        function toByteArray(): byte_Array1d; virtual;
    end;

    ByteArrayStream = class(InputOutputStream)
    protected
        count: int;
        pos: int;
        buf: byte_Array1d;
    public
        constructor create(); overload;
        constructor create(const buf: byte_Array1d; count: int); overload;
        function seekSupported(): boolean; override;
        function size(): long; override;
        function position(): long; override;
        function available(): long; override;
        function seek(delta: long): long; override;
        function read(const dst: byte_Array1d; offset, length: int): int; overload; override;
        function read(): int; overload; override;
        function write(const src: byte_Array1d; offset, length: int): int; overload; override;
        function write(value: int): boolean; overload; override;
        function truncate(): long; override;
        function toByteArray(): byte_Array1d; virtual;
    end;
{%endregion}

{%region routine }
    procedure copyBytes(inputStream: Input; outputStream: Output; bytesCount: long);
    function readUTF(input: DataInput): UnicodeString;
    function readExtUTF(input: DataInput): UnicodeString;
    function writeUTF(output: DataOutput; const s: UnicodeString): int;
    function writeExtUTF(output: DataOutput; const s: UnicodeString): int;
{%endregion}

implementation

{%region routine }
    function readUTFchars(input: DataInput; length: int): UnicodeString;
    var
        strlen: int;
        b1: int;
        b2: int;
        b3: int;
        i: int;
        src: byte_Array1d;
        res: wchar_Array1d;
    begin
        strlen := 0;
        src := byte_Array1d_create(length);
        res := wchar_Array1d_create(length);
        input.readFully(src);
        i := 0;
        while i < length do begin
            b1 := int(src[i]) and $ff;
            case b1 shr 4 of
            $00..$07: begin
                inc(i);
                res[strlen] := wchar(b1);
                inc(strlen);
            end;
            $0c..$0d: begin
                inc(i, 2);
                if i > length then begin
                    raise UTFDataFormatException.create('Ошибка в данных, закодированных кодировкой UTF-8.');
                end;
                b2 := src[i - 1];
                if (b2 and $c0) <> $80 then begin
                    raise UTFDataFormatException.create('Ошибка в данных, закодированных кодировкой UTF-8.');
                end;
                res[strlen] := wchar(((b1 and $1f) shl 6) or (b2 and $3f));
                inc(strlen);
            end;
            $0e: begin
                inc(i, 3);
                if i > length then begin
                    raise UTFDataFormatException.create('Ошибка в данных, закодированных кодировкой UTF-8.');
                end;
                b2 := src[i - 2];
                b3 := src[i - 1];
                if ((b2 and $c0) <> $80) or ((b3 and $c0) <> $80) then begin
                    raise UTFDataFormatException.create('Ошибка в данных, закодированных кодировкой UTF-8.');
                end;
                res[strlen] := wchar(((b1 and $0f) shl 12) or ((b2 and $3f) shl 6) or (b3 and $3f));
                inc(strlen);
            end;
            else
                raise UTFDataFormatException.create('Ошибка в данных, закодированных кодировкой UTF-8.');
            end;
        end;
        result := UnicodeString_create(res, 0, strlen);
    end;

    function getUTFlength(const s: UnicodeString): int;
    var
        c: int;
        i: int;
    begin
        result := 0;
        for i := System.length(s) downto 1 do begin
            c := int(s[i]);
            if (c >= $0001) and (c < $0080) then begin
                inc(result, 1);
            end else
            if c < $0800 then begin
                inc(result, 2);
            end else begin
                inc(result, 3);
            end;
        end;
    end;

    procedure writeUTFchars(output: DataOutput; length: int; const s: UnicodeString);
    var
        res: byte_Array1d;
        i: int;
        j: int;
        c: int;
    begin
        res := byte_Array1d_create(length);
        j := 0;
        for i := 1 to System.length(s) do begin
            c := int(s[i]);
            if (c >= $0001) and (c < $0080) then begin
                res[j] := byte(c);
                inc(j);
            end else
            if c < $0800 then begin
                res[j] := byte($c0 or ((c shr 6) and $1f));
                res[j + 1] := byte($80 or (c and $3f));
                inc(j, 2);
            end else begin
                res[j] := byte($e0 or ((c shr 12) and $0f));
                res[j + 1] := byte($80 or ((c shr 6) and $3f));
                res[j + 2] := byte($80 or (c and $3f));
                inc(j, 3);
            end;
        end;
        output.writeFully(res);
    end;

    procedure copyBytes(inputStream: Input; outputStream: Output; bytesCount: long);
    var
        buffer: byte_Array1d;
        fragment: long;
        fragments: long;
        remainder: long;
    begin
        buffer := byte_Array1d_create($10000);
        fragments := divLong(bytesCount, System.length(buffer), remainder);
        fragment := 0;
        while fragment < fragments do begin
            inputStream.read(buffer);
            outputStream.write(buffer);
            inc(fragment);
        end;
        if int(remainder) > 0 then begin
            inputStream.read(buffer, 0, int(remainder));
            outputStream.write(buffer, 0, int(remainder));
        end;
    end;

    function readUTF(input: DataInput): UnicodeString;
    var
        length: int;
    begin
        length := input.readUnsignedShort();
        if length = 0 then begin
            result := '';
            exit;
        end;
        result := readUTFchars(input, length);
    end;

    function readExtUTF(input: DataInput): UnicodeString;
    var
        length: int;
    begin
        length := input.readUnsignedShort();
        if length = 0 then begin
            result := '';
            exit;
        end;
        if (length and $8000) <> 0 then begin
            length := ((length and $7fff) shl 16) or input.readUnsignedShort();
            if length = 0 then begin
                result := '';
                exit;
            end;
        end;
        result := readUTFchars(input, length);
    end;

    function writeUTF(output: DataOutput; const s: UnicodeString): int;
    var
        length: int;
    begin
        length := getUTFlength(s);
        if length >= $10000 then begin
            raise UTFDataFormatException.create('Длина данных, закодированных кодировкой UTF-8, не может превышать 64 КБ.');
        end;
        output.writeShort(length);
        writeUTFchars(output, length, s);
        result := length + 2;
    end;

    function writeExtUTF(output: DataOutput; const s: UnicodeString): int;
    var
        length: int;
    begin
        length := getUTFlength(s);
        if length >= $8000 then begin
            output.writeInt(length or int($80000000));
            result := length + 4;
        end else begin
            output.writeShort(length);
            result := length + 2;
        end;
        writeUTFchars(output, length, s);
    end;
{%endregion}

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

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

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

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

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

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

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

    function InputStream.seekSupported(): boolean;
    begin
        result := false;
    end;

    function InputStream.available(): long;
    begin
        result := size() - position();
    end;

    function InputStream.seek(delta: long): long;
    begin
        result := 0;
        raise IOException.create('InputStream.seek: класс ' + getClass().getName() + ' не поддерживает метод seek.');
    end;

    function InputStream.read(const dst: byte_Array1d; offset, length: int): int;
    var
        lim: int;
        len: int;
        i: int;
        b: int;
    begin
        lim := offset + length;
        len := System.length(dst);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('InputStream.read: индекс элемента массива выходит из диапазона.');
        end;
        for i := 0 to length - 1 do begin
            b := read();
            if b >= 0 then begin
                dst[offset + i] := byte(b);
            end else begin
                result := i;
                exit;
            end;
        end;
        result := length;
    end;

    function InputStream.read(const dst: byte_Array1d): int;
    begin
        result := read(dst, 0, length(dst));
    end;
{%endregion}

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

    function OutputStream.write(const src: byte_Array1d; offset, length: int): int;
    var
        lim: int;
        len: int;
        i: int;
    begin
        lim := offset + length;
        len := System.length(src);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('OutputStream.write: индекс элемента массива выходит из диапазона.');
        end;
        for i := 0 to length - 1 do begin
            if not write(src[offset + i]) then begin
                result := i;
                exit;
            end;
        end;
        result := length;
    end;

    function OutputStream.write(const src: byte_Array1d): int;
    begin
        result := write(src, 0, length(src));
    end;
{%endregion}

{%region DataInputStream }
    constructor DataInputStream.create(stream: Input);
    begin
        inherited create();
        self.stream := stream;
        self.buffer := byte_Array1d_create(4);
    end;

    function DataInputStream.seekSupported(): boolean;
    begin
        result := stream.seekSupported();
    end;

    function DataInputStream.size(): long;
    begin
        result := stream.size();
    end;

    function DataInputStream.position(): long;
    begin
        result := stream.position();
    end;

    function DataInputStream.available(): long;
    begin
        result := stream.available();
    end;

    function DataInputStream.seek(delta: long): long;
    begin
        result := stream.seek(delta);
    end;

    function DataInputStream.read(const dst: byte_Array1d; offset, length: int): int;
    begin
        result := stream.read(dst, offset, length);
    end;

    function DataInputStream.read(const dst: byte_Array1d): int;
    begin
        result := stream.read(dst);
    end;

    function DataInputStream.read(): int;
    begin
        result := stream.read();
    end;

    function DataInputStream.readBoolean(): boolean;
    var
        b: int;
    begin
        b := read();
        if b < 0 then begin
            raise EOFException.create('Поток ввода данных: достигнут конец данных.');
        end;
        result := b <> 0;
    end;

    function DataInputStream.readByte(): int;
    var
        b: int;
    begin
        b := read();
        if b < 0 then begin
            raise EOFException.create('Поток ввода данных: достигнут конец данных.');
        end;
        result := byte(b);
    end;

    function DataInputStream.readShort(): int;
    var
        b: byte_Array1d;
    begin
        b := buffer;
        if read(b, 0, 2) < 2 then begin
            raise EOFException.create('Поток ввода данных: достигнут конец данных.');
        end;
        result := short(((b[0] and $ff) shl 8) or (b[1] and $ff));
    end;

    function DataInputStream.readShortLE(): int;
    var
        b: byte_Array1d;
    begin
        b := buffer;
        if read(b, 0, 2) < 2 then begin
            raise EOFException.create('Поток ввода данных: достигнут конец данных.');
        end;
        result := short(((b[1] and $ff) shl 8) or (b[0] and $ff));
    end;

    function DataInputStream.readInt(): int;
    var
        b: byte_Array1d;
    begin
        b := buffer;
        if read(b, 0, 4) < 4 then begin
            raise EOFException.create('Поток ввода данных: достигнут конец данных.');
        end;
        result := ((b[0] and $ff) shl 24) or ((b[1] and $ff) shl 16) or ((b[2] and $ff) shl 8) or (b[3] and $ff);
    end;

    function DataInputStream.readIntLE(): int;
    var
        b: byte_Array1d;
    begin
        b := buffer;
        if read(b, 0, 4) < 4 then begin
            raise EOFException.create('Поток ввода данных: достигнут конец данных.');
        end;
        result := ((b[3] and $ff) shl 24) or ((b[2] and $ff) shl 16) or ((b[1] and $ff) shl 8) or (b[0] and $ff);
    end;

    function DataInputStream.readLong(): long;
    var
        i1: int;
        i2: int;
    begin
        i1 := readInt();
        i2 := readInt();
        result := buildLong(i2, i1);
    end;

    function DataInputStream.readLongLE(): long;
    var
        i1: int;
        i2: int;
    begin
        i1 := readIntLE();
        i2 := readIntLE();
        result := buildLong(i1, i2);
    end;

    function DataInputStream.readFloat(): real;
    begin
        result := toReal(intBitsToFloat(readInt()));
    end;

    function DataInputStream.readFloatLE(): real;
    begin
        result := toReal(intBitsToFloat(readIntLE()));
    end;

    function DataInputStream.readDouble(): real;
    begin
        result := toReal(longBitsToDouble(readLong()));
    end;

    function DataInputStream.readDoubleLE(): real;
    begin
        result := toReal(longBitsToDouble(readLongLE()));
    end;

    function DataInputStream.readReal(): real;
    var
        significand: long;
        exponentAndSign: int;
    begin
        exponentAndSign := readUnsignedShort();
        significand := readLong();
        result := buildReal(exponentAndSign, significand);
    end;

    function DataInputStream.readRealLE(): real;
    var
        significand: long;
        exponentAndSign: int;
    begin
        significand := readLongLE();
        exponentAndSign := readUnsignedShortLE();
        result := buildReal(exponentAndSign, significand);
    end;

    function DataInputStream.readUnsignedByte(): int;
    var
        b: int;
    begin
        b := read();
        if b < 0 then begin
            raise EOFException.create('Поток ввода данных: достигнут конец данных.');
        end;
        result := b;
    end;

    function DataInputStream.readUnsignedShort(): int;
    var
        b1: int;
        b2: int;
    begin
        b1 := read();
        b2 := read();
        if (b1 or b2) < 0 then begin
            raise EOFException.create('Поток ввода данных: достигнут конец данных.');
        end;
        result := (b1 shl 8) or b2;
    end;

    function DataInputStream.readUnsignedShortLE(): int;
    var
        b1: int;
        b2: int;
    begin
        b1 := read();
        b2 := read();
        if (b1 or b2) < 0 then begin
            raise EOFException.create('Поток ввода данных: достигнут конец данных.');
        end;
        result := (b2 shl 8) or b1;
    end;

    function DataInputStream.readChar(): wchar;
    begin
        result := wchar(readUnsignedShort());
    end;

    function DataInputStream.readCharLE(): wchar;
    begin
        result := wchar(readUnsignedShortLE());
    end;

    function DataInputStream.readUTF(): UnicodeString;
    begin
        result := IOStreams.readUTF(self);
    end;

    function DataInputStream.readExtUTF(): UnicodeString;
    begin
        result := IOStreams.readExtUTF(self);
    end;

    procedure DataInputStream.readFully(const dst: byte_Array1d);
    begin
        readFully(dst, 0, length(dst));
    end;

    procedure DataInputStream.readFully(const dst: byte_Array1d; offset, length: int);
    var
        lim: int;
        len: int;
        c: int;
        n: int;
    begin
        lim := offset + length;
        len := System.length(dst);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('DataInputStream.readFully: индекс элемента массива выходит из диапазона.');
        end;
        n := 0;
        while n < length do begin
            c := read(dst, offset + n, length - n);
            if c < 0 then begin
                raise EOFException.create('Поток ввода данных: достигнут конец данных.');
            end;
            inc(n, c);
        end;
    end;

    procedure DataInputStream.skipBytes(count: int);
    begin
        while (count > 0) and (read() >= 0) do begin
            dec(count);
        end;
        if count > 0 then begin
            raise EOFException.create('Поток ввода данных: достигнут конец данных.');
        end;
    end;
{%endregion}

{%region DataOutputStream }
    constructor DataOutputStream.create(stream: Output);
    begin
        inherited create();
        self.stream := stream;
    end;

    function DataOutputStream.write(const src: byte_Array1d; offset, length: int): int;
    begin
        result := stream.write(src, offset, length);
    end;

    function DataOutputStream.write(const src: byte_Array1d): int;
    begin
        result := stream.write(src);
    end;

    function DataOutputStream.write(value: int): boolean;
    begin
        result := stream.write(value);
    end;

    procedure DataOutputStream.writeBoolean(a: boolean);
    var
        v: int;
    begin
        if a then begin
            v := 1;
        end else begin
            v := 0;
        end;
        if not write(v) then begin
            raise IOException.create('Поток вывода данных: не удалось записать данные.');
        end;
    end;

    procedure DataOutputStream.writeByte(a: int);
    begin
        if not write(a) then begin
            raise IOException.create('Поток вывода данных: не удалось записать данные.');
        end;
    end;

    procedure DataOutputStream.writeShort(a: int);
    begin
        if not write(a shr 8) or not write(a) then begin
            raise IOException.create('Поток вывода данных: не удалось записать данные.');
        end;
    end;

    procedure DataOutputStream.writeShortLE(a: int);
    begin
        if not write(a) or not write(a shr 8) then begin
            raise IOException.create('Поток вывода данных: не удалось записать данные.');
        end;
    end;

    procedure DataOutputStream.writeInt(a: int);
    begin
        if not write(a shr 24) or not write(a shr 16) or not write(a shr 8) or not write(a) then begin
            raise IOException.create('Поток вывода данных: не удалось записать данные.');
        end;
    end;

    procedure DataOutputStream.writeIntLE(a: int);
    begin
        if not write(a) or not write(a shr 8) or not write(a shr 16) or not write(a shr 24) then begin
            raise IOException.create('Поток вывода данных: не удалось записать данные.');
        end;
    end;

    procedure DataOutputStream.writeLong(a: long);
    begin
        writeInt(LongRecord(a).hi);
        writeInt(LongRecord(a).lo);
    end;

    procedure DataOutputStream.writeLongLE(a: long);
    begin
        writeIntLE(LongRecord(a).lo);
        writeIntLE(LongRecord(a).hi);
    end;

    procedure DataOutputStream.writeFloat(a: real);
    begin
        writeInt(floatToIntBits(toFloat(a)));
    end;

    procedure DataOutputStream.writeFloatLE(a: real);
    begin
        writeIntLE(floatToIntBits(toFloat(a)));
    end;

    procedure DataOutputStream.writeDouble(a: real);
    begin
        writeLong(doubleToLongBits(toDouble(a)));
    end;

    procedure DataOutputStream.writeDoubleLE(a: real);
    begin
        writeLongLE(doubleToLongBits(toDouble(a)));
    end;

    procedure DataOutputStream.writeReal(a: real);
    begin
        writeShort(extractExponentAndSign(a));
        writeLong(extractSignificand(a));
    end;

    procedure DataOutputStream.writeRealLE(a: real);
    begin
        writeLongLE(extractSignificand(a));
        writeShortLE(extractExponentAndSign(a));
    end;

    procedure DataOutputStream.writeChar(a: wchar);
    begin
        writeShort(int(a));
    end;

    procedure DataOutputStream.writeCharLE(a: wchar);
    begin
        writeShortLE(int(a));
    end;

    procedure DataOutputStream.writeChars(const s: AnsiString);
    begin
        write(stringToByteArray(s));
    end;

    procedure DataOutputStream.writeUTF(const s: UnicodeString);
    begin
        IOStreams.writeUTF(self, s);
    end;

    procedure DataOutputStream.writeExtUTF(const s: UnicodeString);
    begin
        IOStreams.writeExtUTF(self, s);
    end;

    procedure DataOutputStream.writeFully(const src: byte_Array1d);
    begin
        writeFully(src, 0, length(src));
    end;

    procedure DataOutputStream.writeFully(const src: byte_Array1d; offset, length: int);
    var
        lim: int;
        len: int;
        c: int;
        n: int;
    begin
        lim := offset + length;
        len := System.length(src);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('DataOutputStream.writeFully: индекс элемента массива выходит из диапазона.');
        end;
        n := 0;
        while n < length do begin
            c := write(src, offset + n, length - n);
            if c < 0 then begin
                raise EOFException.create('Поток ввода данных: достигнут конец данных.');
            end;
            inc(n, c);
        end;
    end;
{%endregion}

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

    function InputOutputStream.seekSupported(): boolean;
    begin
        result := false;
    end;

    function InputOutputStream.available(): long;
    begin
        result := size() - position();
    end;

    function InputOutputStream.seek(delta: long): long;
    begin
        result := 0;
        raise IOException.create('InputOutputStream.seek: класс ' + getClass().getName() + ' не поддерживает метод seek.');
    end;

    function InputOutputStream.read(const dst: byte_Array1d; offset, length: int): int;
    var
        lim: int;
        len: int;
        i: int;
        b: int;
    begin
        lim := offset + length;
        len := System.length(dst);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('InputOutputStream.read: индекс элемента массива выходит из диапазона.');
        end;
        for i := 0 to length - 1 do begin
            b := read();
            if b >= 0 then begin
                dst[offset + i] := byte(b);
            end else begin
                result := i;
                exit;
            end;
        end;
        result := length;
    end;

    function InputOutputStream.read(const dst: byte_Array1d): int;
    begin
        result := read(dst, 0, length(dst));
    end;

    function InputOutputStream.write(const src: byte_Array1d; offset, length: int): int;
    var
        lim: int;
        len: int;
        i: int;
    begin
        lim := offset + length;
        len := System.length(src);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('InputOutputStream.write: индекс элемента массива выходит из диапазона.');
        end;
        for i := 0 to length - 1 do begin
            if not write(src[offset + i]) then begin
                result := i;
                exit;
            end;
        end;
        result := length;
    end;

    function InputOutputStream.write(const src: byte_Array1d): int;
    begin
        result := write(src, 0, length(src));
    end;

    function InputOutputStream.getDataInput(): DataInput;
    begin
        result := DataInputStream.create(self);
    end;

    function InputOutputStream.getDataOutput(): DataOutput;
    begin
        result := DataOutputStream.create(self);
    end;
{%endregion}

{%region ByteArrayInputStream }
    constructor ByteArrayInputStream.create(const buf: byte_Array1d);
    begin
        create(buf, 0, length(buf));
    end;

    constructor ByteArrayInputStream.create(const buf: byte_Array1d; offset, length: int);
    begin
        inherited create();
        self.start := offset;
        self.count := min(offset + length, System.length(buf));
        self.pos := offset;
        self.buf := buf;
    end;

    function ByteArrayInputStream.seekSupported(): boolean;
    begin
        result := true;
    end;

    function ByteArrayInputStream.size(): long;
    begin
        result := long(count) - long(start);
    end;

    function ByteArrayInputStream.position(): long;
    begin
        result := long(pos) - long(start);
    end;

    function ByteArrayInputStream.available(): long;
    begin
        result := long(count) - long(pos);
    end;

    function ByteArrayInputStream.seek(delta: long): long;
    var
        s: long;
        p: long;
    begin
        s := start;
        p := max(s, min(long(pos) + delta, long(count)));
        pos := int(p);
        result := p - s;
    end;

    function ByteArrayInputStream.read(const dst: byte_Array1d; offset, length: int): int;
    var
        lim: int;
        len: int;
        p: int;
    begin
        lim := offset + length;
        len := System.length(dst);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('ByteArrayInputStream.read: индекс элемента массива выходит из диапазона.');
        end;
        p := pos;
        result := min(count - p, length);
        arraycopy(buf, p, dst, offset, result);
        pos := p + result;
    end;

    function ByteArrayInputStream.read(): int;
    var
        p: int;
    begin
        p := pos;
        if p < count then begin
            result := int(buf[p]) and $ff;
            pos := p + 1;
        end else begin
            result := -1;
        end;
    end;
{%endregion}

{%region ByteArrayOutputStream }
    constructor ByteArrayOutputStream.create();
    begin
        inherited create();
        count := 0;
        buf := byte_Array1d_create(16);
    end;

    function ByteArrayOutputStream.write(const src: byte_Array1d; offset, length: int): int;
    var
        lim: int;
        len: int;
        cap: int;
        nc: int;
        c: int;
        b: byte_Array1d;
        t: byte_Array1d;
    begin
        lim := offset + length;
        len := System.length(src);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('InputOutputStream.write: индекс элемента массива выходит из диапазона.');
        end;
        b := buf;
        c := count;
        nc := c + length;
        cap := System.length(b);
        if nc > cap then begin
            t := byte_Array1d_create(max(cap shl 1, nc));
            arraycopy(b, 0, t, 0, c);
            b := t;
            buf := t;
        end;
        arraycopy(src, offset, b, c, length);
        count := nc;
        result := length;
    end;

    function ByteArrayOutputStream.write(value: int): boolean;
    var
        cap: int;
        nc: int;
        c: int;
        b: byte_Array1d;
        t: byte_Array1d;
    begin
        b := buf;
        c := count;
        nc := c + 1;
        cap := System.length(b);
        if nc > cap then begin
            t := byte_Array1d_create(max(cap shl 1, nc));
            arraycopy(b, 0, t, 0, c);
            b := t;
            buf := t;
        end;
        b[c] := byte(value);
        count := nc;
        result := true;
    end;

    function ByteArrayOutputStream.toByteArray(): byte_Array1d;
    var
        c: int;
        b: byte_Array1d;
    begin
        b := buf;
        c := count;
        if c <> System.length(b) then begin
            result := byte_Array1d_create(c);
            arraycopy(b, 0, result, 0, c);
        end else begin
            result := b;
        end;
    end;
{%endregion}

{%region ByteArrayStream }
    constructor ByteArrayStream.create();
    begin
        inherited create();
        self.buf := byte_Array1d_create(16);
    end;

    constructor ByteArrayStream.create(const buf: byte_Array1d; count: int);
    begin
        inherited create();
        self.count := min(count, System.length(buf));
        self.buf := buf;
    end;

    function ByteArrayStream.seekSupported(): boolean;
    begin
        result := true;
    end;

    function ByteArrayStream.size(): long;
    begin
        result := long(count);
    end;

    function ByteArrayStream.position(): long;
    begin
        result := long(pos);
    end;

    function ByteArrayStream.available(): long;
    begin
        result := long(count) - long(pos);
    end;

    function ByteArrayStream.seek(delta: long): long;
    begin
        result := max(0, min(long(pos) + delta, long(count)));
        pos := int(result);
    end;

    function ByteArrayStream.read(const dst: byte_Array1d; offset, length: int): int;
    var
        lim: int;
        len: int;
        p: int;
    begin
        lim := offset + length;
        len := System.length(dst);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('ByteArrayStream.read: индекс элемента массива выходит из диапазона.');
        end;
        p := pos;
        result := min(count - p, length);
        arraycopy(buf, p, dst, offset, result);
        pos := p + result;
    end;

    function ByteArrayStream.read(): int;
    var
        p: int;
    begin
        p := pos;
        if p < count then begin
            result := int(buf[p]) and $ff;
            pos := p + 1;
        end else begin
            result := -1;
        end;
    end;

    function ByteArrayStream.write(const src: byte_Array1d; offset, length: int): int;
    var
        lim: int;
        len: int;
        cap: int;
        nc: int;
        c: int;
        b: byte_Array1d;
        t: byte_Array1d;
    begin
        lim := offset + length;
        len := System.length(src);
        if (lim > len) or (lim < offset) or (offset < 0) or (offset > len) then begin
            raise ArrayIndexOutOfBoundsException.create('ByteArrayStream.write: индекс элемента массива выходит из диапазона.');
        end;
        b := buf;
        c := count;
        nc := c + length;
        cap := System.length(b);
        if nc > cap then begin
            t := byte_Array1d_create(max(cap shl 1, nc));
            arraycopy(b, 0, t, 0, c);
            b := t;
            buf := t;
        end;
        arraycopy(src, offset, b, c, length);
        count := nc;
        result := length;
    end;

    function ByteArrayStream.write(value: int): boolean;
    var
        cap: int;
        nc: int;
        c: int;
        b: byte_Array1d;
        t: byte_Array1d;
    begin
        b := buf;
        c := count;
        nc := c + 1;
        cap := System.length(b);
        if nc > cap then begin
            t := byte_Array1d_create(max(cap shl 1, nc));
            arraycopy(b, 0, t, 0, c);
            b := t;
            buf := t;
        end;
        b[c] := byte(value);
        count := nc;
        result := true;
    end;

    function ByteArrayStream.truncate(): long;
    begin
        result := long(pos);
        count := int(result);
    end;

    function ByteArrayStream.toByteArray(): byte_Array1d;
    var
        c: int;
        b: byte_Array1d;
    begin
        b := buf;
        c := count;
        if c <> System.length(b) then begin
            result := byte_Array1d_create(c);
            arraycopy(b, 0, result, 0, c);
        end else begin
            result := b;
        end;
    end;
{%endregion}

end.

