unit ORawByteString;

{

  Author:
    Ondrej Pokorny, http://www.kluug.net
    All Rights Reserved.

  Version
    0.2 2014-04-12

  License:
    MPL 1.1: http://www.mozilla.org/MPL/1.1/

}

{
  ORawByteString.pas

  TORawByteString
    - a replacement of RawByteString for NextGen (iOS/Android) for use
      as a plain byte buffer (no explicit string data inside)
    - please note that all indices used in TORawByteString are 0-based if not stated differently.
      this is necessary to keep things simple

  TORawByteChar
    - a replacement for AnsiChar

  TORawSingleByteEncoding
    - Encoding that converts the byte representation of chars (only within $FF) directly to/from byte
      (no character codepage involved).

}

{.$DEFINE ORAW_IMPLICIT}//define to allow implicit conversions (a little less secure)

{$BOOLEVAL OFF}

interface

uses
  SysUtils, Classes, Generics.Collections;

type
  TORawSingleByteEncoding = class(TEncoding)
  strict private
    class var
      FSavedInstance: TORawSingleByteEncoding;
    class destructor Destroy;
  strict protected
    function GetByteCount(Chars: PChar; CharCount: Integer): Integer; override;
    function GetBytes(Chars: PChar; CharCount: Integer; Bytes: PByte; ByteCount: Integer): Integer; override;
    function GetCharCount(Bytes: PByte; ByteCount: Integer): Integer; override;
    function GetChars(Bytes: PByte; ByteCount: Integer; Chars: PChar; CharCount: Integer): Integer; override;
  public
    constructor Create;
  public
    class function Use: TORawSingleByteEncoding;
    destructor Destroy; override;

    function GetMaxByteCount(CharCount: Integer): Integer; override;
    function GetMaxCharCount(ByteCount: Integer): Integer; override;
    function GetPreamble: TBytes; override;
  end;

  TEncodingHelper = class helper for TEncoding
  public
    class function RawByte: TEncoding;
  end;

  TORawByteChar = packed record
  strict private
    FChar: Byte;
  public
    //operators
    class operator Equal(const a1, a2: TORawByteChar): Boolean;
    class operator Equal(const a1: TORawByteChar; const a2: Char): Boolean;
    class operator Equal(const a1: Char; const a2: TORawByteChar): Boolean;

    class operator NotEqual(const a1, a2: TORawByteChar): Boolean;
    class operator NotEqual(const a1: TORawByteChar; const a2: Char): Boolean;
    class operator NotEqual(const a1: Char; const a2: TORawByteChar): Boolean;

    class operator GreaterThan(const a1, a2: TORawByteChar): Boolean;
    class operator GreaterThan(const a1: TORawByteChar; const a2: Char): Boolean;
    class operator GreaterThan(const a1: Char; const a2: TORawByteChar): Boolean;

    class operator GreaterThanOrEqual(const a1, a2: TORawByteChar): Boolean;
    class operator GreaterThanOrEqual(const a1: TORawByteChar; const a2: Char): Boolean;
    class operator GreaterThanOrEqual(const a1: Char; const a2: TORawByteChar): Boolean;

    class operator LessThan(const a1, a2: TORawByteChar): Boolean;
    class operator LessThan(const a1: TORawByteChar; const a2: Char): Boolean;
    class operator LessThan(const a1: Char; const a2: TORawByteChar): Boolean;

    class operator LessThanOrEqual(const a1, a2: TORawByteChar): Boolean;
    class operator LessThanOrEqual(const a1: TORawByteChar; const a2: Char): Boolean;
    class operator LessThanOrEqual(const a1: Char; const a2: TORawByteChar): Boolean;

    {$IFDEF ORAW_IMPLICIT}
    class operator Implicit(const a: Char): TORawByteChar;
    class operator Implicit(const a: TORawByteChar): Char;
    class operator Implicit(const a: Byte): TORawByteChar;
    class operator Implicit(const a: TORawByteChar): Byte;
    {$ELSE}
    class operator Explicit(const a: Char): TORawByteChar;
    class operator Explicit(const a: TORawByteChar): Char;
    class operator Explicit(const a: Byte): TORawByteChar;
    class operator Explicit(const a: TORawByteChar): Byte;
    {$ENDIF}
  public
    //ANSI functions
    function ToUpper: TORawByteChar;
    function ToLower: TORawByteChar;
  end;
  TORawByteCharArray = array[0..High(Integer)-1] of TORawByteChar;
  PORawByteChar = ^TORawByteCharArray;

  TORawByteString = packed record
  strict private class var
    FDefaultEncoding: TEncoding;//default encoding for AnsiString operations, use TORawSingleByteEncoding
  strict private type
    PORawByteString = ^TORawByteString;
    TORawByteStringCharEnumerator = class
    strict private
      FIndex: Integer;
      FString: PORawByteString;
    public
      constructor Create(aBuffer: PORawByteString);
      function GetCurrent: TORawByteChar; inline;
      function MoveNext: Boolean;
      property Current: TORawByteChar read GetCurrent;
    end;
  strict private
    FStrBuffer: String;//We store the real buffer length in the first two char positions!

    function GetChar(const aIndex: Integer): TORawByteChar;
    procedure SetChar(const aIndex: Integer; const aChar: TORawByteChar);
    function GetDefChar(const aIndex: Integer): TORawByteChar;
    procedure SetDefChar(const aIndex: Integer; const aChar: TORawByteChar);
    function GetBuffer: PByteArray;
    function GetLength: Integer;
    procedure SetLength(const aLength: Integer);

    procedure CopyOnWriteCheck;
  public
    //operators

    class operator Add(const a1, a2: TORawByteString): TORawByteString;
    class operator Add(const a1: TORawByteString; const a2: TBytes): TORawByteString;
    class operator Add(const a1: TBytes; const a2: TORawByteString): TORawByteString;
    class operator Add(const a1: TORawByteString; const a2: TORawByteChar): TORawByteString;
    class operator Add(const a1: TORawByteChar; const a2: TORawByteString): TORawByteString;
    class operator Equal(const a1, a2: TORawByteString): Boolean;
    class operator NotEqual(const a1, a2: TORawByteString): Boolean;

    {$IFDEF ORAW_IMPLICIT}
    class operator Implicit(const a: string): TORawByteString;
    class operator Implicit(const a: TORawByteString): string;
    class operator Implicit(const a: TBytes): TORawByteString;
    class operator Implicit(const a: TORawByteString): TBytes;
    class operator Implicit(const a: TORawByteString): PORawByteChar;
    class operator Implicit(const a: PORawByteChar): TORawByteString;
    {$ELSE}
    class operator Explicit(const a: string): TORawByteString;
    class operator Explicit(const a: TORawByteString): string;
    class operator Explicit(const a: TBytes): TORawByteString;
    class operator Explicit(const a: TORawByteString): TBytes;
    class operator Explicit(const a: TORawByteString): PORawByteChar;
    class operator Explicit(const a: PORawByteChar): TORawByteString;
    {$ENDIF}

    function GetEnumerator: TORawByteStringCharEnumerator;
  public
    //constructors

    constructor Create(const aBytes: TBytes); overload;
    constructor Create(const aString: string; aEncoding: TEncoding = nil); overload;
  public
    //byte functions -> they act on raw byte string as "Array of Byte"

    //clear
    procedure Clear;
    //get the byte position of string, result is 0-based!
    function Pos(const aSubStr: TORawByteString): Integer;
    function PosEx(const aSubStr: TORawByteString; aOffset: Integer): Integer;

    //Append
    procedure Append(const aBytes: TBytes); overload;
    procedure Append(const aBuffer: TORawByteString); overload;
    procedure Append(const aBuffer; aBufferLength: Integer); overload;
    //Prepend
    procedure Prepend(const aBytes: TBytes); overload;
    procedure Prepend(const aBuffer: TORawByteString); overload;
    procedure Prepend(const aBuffer; aBufferLength: Integer); overload;
    //insert into a byte index, 0-based!!!
    procedure Insert(const aSubBytes: TBytes; aIndex: Integer); overload;
    procedure Insert(const aSubBuffer: TORawByteString; aIndex: Integer); overload;
    procedure Insert(const aSubBuffer; aSubBufferLength, aIndex: Integer); overload;

    //copy, 0-based!!!
    function Copy(aIndex, aCount: Integer): TORawByteString;
    //delete, 0-based!!!
    procedure Delete(aIndex, aCount: Integer);

    procedure ToUpper(aEncoding: TEncoding = nil);
    procedure ToLower(aEncoding: TEncoding = nil);
  public
    //String & TBytes conversion utilities
    function GetString(aEncoding: TEncoding = nil): string;
    procedure SetString(const aString: string; aEncoding: TEncoding = nil);
    function GetBytes: TBytes;
    procedure SetBytes(const aBytes: TBytes);
  public
    //stream & file functions
    procedure LoadFromStream(const aStream: TStream);
    procedure LoadFromFile(const aFileName: string);
    procedure SaveToStream(const aStream: TStream; const aWriteBOM: Boolean = True);
    procedure SaveToFile(const aFileName: string; const aWriteBOM: Boolean = True);
  public
    class function GetDefaultEncoding: TEncoding; static;
    class procedure SetDefaultEncoding(aEncoding: TEncoding); static;//default is TORawSingleByteEncoding = RawByteString, if you want to simulate AnsiString, use TEncoding.ANSI
  public
    //properties

    property Length: Integer read GetLength write SetLength;
    //get/set raw bytes by index, 0-based !!!
    property Chars[const aIndex: Integer]: TORawByteChar read GetChar write SetChar;
    //get/set raw bytes by index, 1-based !!!
    property DefChars[const aIndex: Integer]: TORawByteChar read GetDefChar write SetDefChar; default;
  end;

//Ansi* definitions and helper function

{$IFDEF NEXTGEN}
  AnsiString = TORawByteString;
  AnsiChar = TORawByteChar;
  RawByteString = TORawByteString;
  PAnsiChar = PORawByteChar;
  PRawByteChar = PORawByteChar;
{$ENDIF}

//All following string functions are 1-based
function UTF8ToString(const aUTF8String: TORawByteString): String; inline; overload;
function UTF8Decode(const aUTF8String: TORawByteString): String; inline; overload;
function UTF8Encode(const aString: String): TORawByteString; inline; overload;
function Pos(const aSubStr: String; const aString: TORawByteString): Integer; inline; overload;
function Pos(const aSubStr, aString: TORawByteString): Integer; inline; overload;
function PosEx(const aSubStr: String; const aString: TORawByteString; aOffset: Integer): Integer; inline; overload;
function PosEx(const aSubStr, aString: TORawByteString; aOffset: Integer): Integer; inline; overload;
function LowerCase(const aString: TORawByteString): TORawByteString; inline; overload;
function UpperCase(const aString: TORawByteString): TORawByteString; inline; overload;
function CompareStr(const S1, S2: TORawByteString): Integer; inline; overload;
function CompareText(const S1, S2: TORawByteString): Integer; inline; overload;

//the following functions can't be declared without Ansi prefix because they are compiler magic
function AnsiCopy(const aString: TORawByteString; aIndex, aCount: Integer): TORawByteString; inline; overload;
procedure AnsiInsert(const aSubString: TORawByteString; var aString: TORawByteString; aIndex: Integer); inline; overload;
procedure AnsiDelete(var aString: TORawByteString; aIndex, aCount: Integer); inline; overload;
function AnsiLength(const aString: TORawByteString): Integer; inline; overload;
function AnsiLow(const aString: TORawByteString): Integer; inline; overload;
function AnsiHigh(const aString: TORawByteString): Integer; inline; overload;
{$IFNDEF NEXTGEN}
function AnsiCopy(const aString: RawByteString; aIndex, aCount: Integer): RawByteString; inline; overload;
procedure AnsiInsert(const aSubString: RawByteString; var aString: RawByteString; aIndex: Integer); inline; overload;
procedure AnsiDelete(var aString: RawByteString; aIndex, aCount: Integer); inline; overload;
function AnsiLength(const aString: RawByteString): Integer; inline; overload;
function AnsiLow(const aString: RawByteString): Integer; inline; overload;
function AnsiHigh(const aString: RawByteString): Integer; inline; overload;
{$ENDIF}

implementation

function UTF8ToString(const aUTF8String: TORawByteString): String;
begin
  Result := aUTF8String.GetString(TEncoding.UTF8);
end;

function UTF8Decode(const aUTF8String: TORawByteString): String;
begin
  Result := aUTF8String.GetString(TEncoding.UTF8);
end;

function UTF8Encode(const aString: String): TORawByteString;
begin
  Result.SetString(aString, TEncoding.UTF8);
end;

function Pos(const aSubStr: String; const aString: TORawByteString): Integer;
begin
  Result := aString.Pos(TORawByteString(aSubStr)) + 1;
end;

function Pos(const aSubStr, aString: TORawByteString): Integer;
begin
  Result := aString.Pos(aSubStr) + 1;
end;

function PosEx(const aSubStr: String; const aString: TORawByteString; aOffset: Integer): Integer; inline; overload;
begin
  Result := aString.PosEx(TORawByteString(aSubStr), aOffset - 1) + 1;
end;

function PosEx(const aSubStr, aString: TORawByteString; aOffset: Integer): Integer; inline; overload;
begin
  Result := aString.PosEx(aSubStr, aOffset - 1) + 1;
end;

function LowerCase(const aString: TORawByteString): TORawByteString;
begin
  Result := aString;
  Result.ToLower;
end;

function UpperCase(const aString: TORawByteString): TORawByteString;
begin
  Result := aString;
  Result.ToUpper;
end;

function CompareStr(const S1, S2: TORawByteString): Integer;
begin
  Result := SysUtils.CompareStr(S1.GetString, S2.GetString);
end;

function CompareText(const S1, S2: TORawByteString): Integer;
begin
  Result := SysUtils.CompareText(S1.GetString, S2.GetString);
end;

function AnsiCopy(const aString: TORawByteString; aIndex, aCount: Integer): TORawByteString;
begin
  Result := aString.Copy(aIndex - 1, aCount);
end;

procedure AnsiInsert(const aSubString: TORawByteString; var aString: TORawByteString; aIndex: Integer);
begin
  aString.Insert(aSubString, aIndex-1);
end;

procedure AnsiDelete(var aString: TORawByteString; aIndex, aCount: Integer);
begin
  aString.Delete(aIndex-1, aCount);
end;

function AnsiLength(const aString: TORawByteString): Integer;
begin
  Result := aString.Length;
end;

function AnsiLow(const aString: TORawByteString): Integer;
begin
  Result := 1;
end;

function AnsiHigh(const aString: TORawByteString): Integer;
begin
  Result := aString.Length;;
end;

{$IFNDEF NEXTGEN}
function AnsiCopy(const aString: RawByteString; aIndex, aCount: Integer): RawByteString;
begin
  Result := System.Copy(aString, aIndex, aCount);
end;

procedure AnsiInsert(const aSubString: RawByteString; var aString: RawByteString; aIndex: Integer);
begin
  System.Insert(aSubString, aString, aIndex);
end;

procedure AnsiDelete(var aString: RawByteString; aIndex, aCount: Integer);
begin
  System.Delete(aString, aIndex, aCount);
end;

function AnsiLength(const aString: RawByteString): Integer;
begin
  Result := System.Length(aString);
end;

function AnsiLow(const aString: RawByteString): Integer;
begin
  Result := 1;
end;

function AnsiHigh(const aString: RawByteString): Integer;
begin
  Result := Length(aString);
end;
{$ENDIF}

{ TORawByteString }

class operator TORawByteString.Add(const a1, a2: TORawByteString): TORawByteString;
begin
  Result := a1;
  Result.Append(a2);
end;

class operator TORawByteString.Add(const a1: TORawByteString;
  const a2: TBytes): TORawByteString;
begin
  Result := a1;
  Result.Append(a2);
end;

class operator TORawByteString.Add(const a1: TBytes;
  const a2: TORawByteString): TORawByteString;
begin
  Result := a2;
  Result.Prepend(a1);
end;

class operator TORawByteString.Add(const a1: TORawByteString;
  const a2: TORawByteChar): TORawByteString;
begin
  Result.Length := a1.Length + 1;
  if a1.Length > 0 then
    System.Move(a1.GetBuffer[0], Result.GetBuffer[0], a1.Length);//we don't need COW check, writing to result
  Result[Result.Length-1] := a2;
end;

class operator TORawByteString.Add(const a1: TORawByteChar;
  const a2: TORawByteString): TORawByteString;
begin
  Result := a2 + a1;
end;

procedure TORawByteString.Append(const aBuffer;
  aBufferLength: Integer);
var
  xOldLength: Integer;
begin
  if aBufferLength > 0 then
  begin
    xOldLength := Length;
    Length := Length + aBufferLength;

    System.Move(aBuffer, GetBuffer[xOldLength], aBufferLength);//we don't need COW check, SetLength does it
  end;
end;

procedure TORawByteString.Append(
  const aBuffer: TORawByteString);
begin
  if aBuffer.Length > 0 then
    Append(aBuffer.GetBuffer[0], aBuffer.Length)
end;

procedure TORawByteString.Append(const aBytes: TBytes);
begin
  if System.Length(aBytes) > 0 then
    Append(aBytes[0], System.Length(aBytes))
end;

procedure TORawByteString.Clear;
begin
  SetLength(0);
end;

function TORawByteString.Copy(aIndex, aCount: Integer): TORawByteString;
begin
  Assert((aIndex >= 0) and (aIndex + aCount <= Length));

  Result.Length := aCount;
  if aCount > 0 then
    System.Move(GetBuffer[aIndex], Result.GetBuffer[0], aCount);//we don't need COW check, writing to result
end;

procedure TORawByteString.CopyOnWriteCheck;
  function RefCount: Integer;
  var
    xStrPtr: ^Integer;
  begin
    //get reference count of FStrBuffer, correct results on 32bit, 64bit and also mobile
    xStrPtr := Pointer(PChar(FStrBuffer));
    Dec(xStrPtr, 2);
    Result := xStrPtr^;
  end;
begin
  if (FStrBuffer <> '') and (RefCount > 1) then//if RefCount is only 1, we don't need CopyOnWrite
    FStrBuffer := System.Copy(FStrBuffer, 1, System.Length(FStrBuffer));//Simulate COW
end;

constructor TORawByteString.Create(const aString: string;
  aEncoding: TEncoding);
begin
  SetString(aString, aEncoding);
end;

procedure TORawByteString.Delete(aIndex, aCount: Integer);
begin
  Assert((aIndex >= 0) and (aIndex + aCount <= Length));

  if aCount <= 0 then
    Exit;

  CopyOnWriteCheck;//we need copy on write check here because Move() is performed on Self before SetLength!

  if aIndex+aCount < Length then
    System.Move(GetBuffer[aIndex+aCount], GetBuffer[aIndex], Length-(aIndex+aCount));

  Length := Length-aCount;
end;

constructor TORawByteString.Create(const aBytes: TBytes);
begin
  SetBytes(aBytes);
end;

class operator TORawByteString.Equal(const a1, a2: TORawByteString): Boolean;
var
  xL1, xL2: Integer;
begin
  //optimization -> compare memory
  xL1 := a1.Length;
  xL2 := a2.Length;

  Result :=
    (a1.GetBuffer = a2.GetBuffer) or (//same string reference
      (xL1 = xL2) and
      ((xL1 = 0) or (CompareMem(a1.GetBuffer, a2.GetBuffer, xL1))));
end;

{$IFDEF ORAW_IMPLICIT}
class operator TORawByteString.Implicit(const a: TORawByteString): PORawByteChar;
{$ELSE}
class operator TORawByteString.Explicit(const a: TORawByteString): PORawByteChar;
{$ENDIF}
begin
  if a.Length > 0 then
  begin
    Result := PORawByteChar(a.GetBuffer);
  end else
    Result := nil;
end;

function TORawByteString.GetBuffer: PByteArray;
begin
  if FStrBuffer <> '' then
    Result := PByteArray(@PChar(FStrBuffer)[2])//real buffer length is stored in the first 2 char positions
  else
    Result := nil;
end;

function TORawByteString.GetBytes: TBytes;
begin
  System.SetLength(Result, Length);
  if Length > 0 then
    System.Move(GetBuffer[0], Result[0], Length);//we don't need COW check, writing to result
end;

function TORawByteString.GetEnumerator: TORawByteStringCharEnumerator;
begin
  Result := TORawByteStringCharEnumerator.Create(@Self);
end;

function TORawByteString.GetLength: Integer;
begin
  if FStrBuffer = '' then//must be here -> FBufferByteLength may be uninitialized
    Result := 0
  else
    Result := PInteger(PChar(FStrBuffer))^;//real buffer length is stored in the first 2 char positions
end;

function TORawByteString.GetChar(const aIndex: Integer): TORawByteChar;
begin
  Result := TORawByteChar(GetBuffer[aIndex]);
end;

class function TORawByteString.GetDefaultEncoding: TEncoding;
begin
  if not Assigned(FDefaultEncoding) then
    FDefaultEncoding := TEncoding.RawByte;

  Result := FDefaultEncoding;
end;

function TORawByteString.GetDefChar(const aIndex: Integer): TORawByteChar;
begin
  Result := GetChar(aIndex-1);
end;

type
  TEncodingProt = class(TEncoding)
  public
    function GetCharCountProt(Bytes: PByte; ByteCount: Integer): Integer;
    function GetCharsProt(Bytes: PByte; ByteCount: Integer; Chars: PChar; CharCount: Integer): Integer;
    function GetByteCountProt(Chars: PChar; CharCount: Integer): Integer;
    function GetBytesProt(Chars: PChar; CharCount: Integer; Bytes: PByte; ByteCount: Integer): Integer;
  end;

function TEncodingProt.GetCharCountProt(Bytes: PByte; ByteCount: Integer): Integer;
begin
  Result := GetCharCount(Bytes, ByteCount);
end;

function TEncodingProt.GetCharsProt(Bytes: PByte; ByteCount: Integer; Chars: PChar; CharCount: Integer): Integer;
begin
  Result := GetChars(Bytes, ByteCount, Chars, CharCount);
end;

function TEncodingProt.GetByteCountProt(Chars: PChar; CharCount: Integer): Integer;
begin
  Result := GetByteCount(Chars, CharCount);
end;

function TEncodingProt.GetBytesProt(Chars: PChar; CharCount: Integer; Bytes: PByte; ByteCount: Integer): Integer;
begin
  Result := GetBytes(Chars, CharCount, Bytes, ByteCount);
end;

function TORawByteString.GetString(aEncoding: TEncoding): string;
var
  xCharCount: Integer;
begin
  if not Assigned(aEncoding) then
    aEncoding := GetDefaultEncoding;

  xCharCount := TEncodingProt(aEncoding).GetCharCountProt(PByte(GetBuffer), Length);
  System.SetLength(Result, xCharCount);
  if xCharCount = 0 then
    Exit('');
  TEncodingProt(aEncoding).GetCharsProt(PByte(GetBuffer), Length, PChar(Result), xCharCount);//we don't need COW check, writing to result
end;

{$IFDEF ORAW_IMPLICIT}
class operator TORawByteString.Implicit(const a: TBytes): TORawByteString;
{$ELSE}
class operator TORawByteString.Explicit(const a: TBytes): TORawByteString;
{$ENDIF}
begin
  Result.SetBytes(a);
end;

{$IFDEF ORAW_IMPLICIT}
class operator TORawByteString.Implicit(const a: TORawByteString): TBytes;
{$ELSE}
class operator TORawByteString.Explicit(const a: TORawByteString): TBytes;
{$ENDIF}
begin
  Result := a.GetBytes;
end;

procedure TORawByteString.Insert(const aSubBuffer: TORawByteString;
  aIndex: Integer);
begin
  if aSubBuffer.Length > 0 then
    Insert(aSubBuffer.GetBuffer[0], aSubBuffer.Length, aIndex)
end;

procedure TORawByteString.Insert(const aSubBytes: TBytes;
  aIndex: Integer);
begin
  if System.Length(aSubBytes) > 0 then
    Insert(aSubBytes[0], System.Length(aSubBytes), aIndex)
end;

procedure TORawByteString.Insert(const aSubBuffer;
  aSubBufferLength, aIndex: Integer);
var
  xOldLength: Integer;
begin
  if aSubBufferLength > 0 then
  begin
    Assert((aIndex >= 0) and (aIndex <= Length));

    xOldLength := Length;
    Length := Length + aSubBufferLength;
    if aIndex < xOldLength then
      System.Move(GetBuffer[aIndex], GetBuffer[aIndex+aSubBufferLength], xOldLength-aIndex);//we don't need COW check, SetLength does it
    System.Move(aSubBuffer, GetBuffer[aIndex], aSubBufferLength);
  end;
end;

{$IFDEF ORAW_IMPLICIT}
class operator TORawByteString.Implicit(const a: string): TORawByteString;
{$ELSE}
class operator TORawByteString.Explicit(const a: string): TORawByteString;
{$ENDIF}
begin
  Result.SetString(a);
end;

{$IFDEF ORAW_IMPLICIT}
class operator TORawByteString.Implicit(const a: TORawByteString): string;
{$ELSE}
class operator TORawByteString.Explicit(const a: TORawByteString): string;
{$ENDIF}
begin
  Result := a.GetString;
end;

procedure TORawByteString.LoadFromFile(const aFileName: string);
var
  xS: TStream;
begin
  xS := TFileStream.Create(aFileName, fmOpenRead or fmShareDenyWrite);
  try
    LoadFromStream(xS);
  finally
    xS.Free;
  end;
end;

procedure TORawByteString.LoadFromStream(const aStream: TStream);
begin
  Length := aStream.Size-aStream.Position;
  if Length > 0 then
  begin
    CopyOnWriteCheck;//we need COW check, Length may stay unchanged!!!
    aStream.ReadBuffer(GetBuffer[0], Length);
  end;
end;

class operator TORawByteString.NotEqual(const a1, a2: TORawByteString): Boolean;
begin
  Result := not(a1 = a2);
end;

function TORawByteString.Pos(const aSubStr: TORawByteString): Integer;
begin
  Result := PosEx(aSubStr, 0);
end;

function TORawByteString.PosEx(const aSubStr: TORawByteString; aOffset: Integer): Integer;
var
  xSubBuffer: PByteArray;
  xSubLength: Integer;
  xSelfBuffer: PByteArray;
  xSelfLength: Integer;
begin
  Assert((aSubStr.Length > 0) and (aOffset >= 0));

  xSelfBuffer := GetBuffer;
  xSelfLength := Length;
  xSubBuffer := aSubStr.GetBuffer;
  xSubLength := aSubStr.Length;
  while aOffset <= (xSelfLength-xSubLength) do
  begin
    if CompareMem(@xSubBuffer[0], @xSelfBuffer[aOffset], xSubLength) then
    begin
      Result := aOffset;
      Exit;
    end;
    Inc(aOffset);
  end;
  Result := -1;
end;

procedure TORawByteString.Prepend(const aBuffer;
  aBufferLength: Integer);
var
  xOldLength: Integer;
begin
  if aBufferLength > 0 then
  begin
    xOldLength := Length;
    Length := Length + aBufferLength;

    if xOldLength > 0 then
      System.Move(GetBuffer[0], GetBuffer[aBufferLength], xOldLength);//we don't need COW check, SetLength does it
    if aBufferLength > 0 then
      System.Move(aBuffer, GetBuffer[0], aBufferLength);
  end;
end;

procedure TORawByteString.Prepend(
  const aBuffer: TORawByteString);
begin
  if aBuffer.Length > 0 then
    Prepend(aBuffer.GetBuffer[0], aBuffer.Length)
end;

procedure TORawByteString.Prepend(const aBytes: TBytes);
begin
  if System.Length(aBytes) > 0 then
    Prepend(aBytes[0], System.Length(aBytes))
end;

procedure TORawByteString.SaveToFile(const aFileName: string;
  const aWriteBOM: Boolean);
var
  xS: TStream;
begin
  xS := TFileStream.Create(aFileName, fmCreate);
  try
    SaveToStream(xS, aWriteBOM);
  finally
    xS.Free;
  end;
end;

procedure TORawByteString.SaveToStream(const aStream: TStream;
  const aWriteBOM: Boolean);
begin
  if Length > 0 then
    aStream.WriteBuffer(GetBuffer[0], Length);
end;

procedure TORawByteString.SetBytes(const aBytes: TBytes);
begin
  Length := System.Length(aBytes);
  if Length > 0 then
  begin
    CopyOnWriteCheck;//we need COW check, the length may stay unchanged
    System.Move(aBytes[0], GetBuffer[0], Length);
  end;
end;

procedure TORawByteString.SetLength(const aLength: Integer);
var
  xNewLength: Integer;
begin
  if aLength = 0 then//empty string, just empty the buffer
    FStrBuffer := ''
  else
  if Length <> aLength then
  begin
    xNewLength := (aLength div 2) + 3;//we need max 3 extra characters (2 for integer length + 1/2 for trailing zero).

    if xNewLength = System.Length(FStrBuffer) then
      CopyOnWriteCheck//byte length is different, but FStrBuffer remained the same -> we need COW
    else
      System.SetLength(FStrBuffer, xNewLength);//different lengths -> Delphi handles COW

    PInteger(PChar(FStrBuffer))^ := aLength;//real buffer length is stored in the first 2 char positions
    GetBuffer[Length] := 0;//write trailing zero (for PAnsiChar support)!
  end;
end;

procedure TORawByteString.SetChar(const aIndex: Integer; const aChar: TORawByteChar);
begin
  Assert((aIndex >= 0) and (aIndex < Length));

  CopyOnWriteCheck;//we need COW check here because we change buffer value of the string
  GetBuffer[aIndex] := Byte(aChar);
end;

class procedure TORawByteString.SetDefaultEncoding(aEncoding: TEncoding);
begin
  FDefaultEncoding := aEncoding;
end;

procedure TORawByteString.SetDefChar(const aIndex: Integer;
  const aChar: TORawByteChar);
begin
  SetChar(aIndex-1, aChar);
end;

procedure TORawByteString.SetString(const aString: string; aEncoding: TEncoding);
begin
  if not Assigned(aEncoding) then
    aEncoding := GetDefaultEncoding;

  Length := TEncodingProt(aEncoding).GetByteCountProt(PChar(aString), System.Length(aString));
  if Length > 0 then
  begin
    CopyOnWriteCheck;//we need COW check here because the length may stay unchanged!
    TEncodingProt(aEncoding).GetBytesProt(PChar(aString), System.Length(aString), PByte(GetBuffer), Length);
  end;
end;

procedure TORawByteString.ToLower(aEncoding: TEncoding);
var
  I: Integer;
  xByte: PByte;
begin
  if not Assigned(aEncoding) then
    aEncoding := GetDefaultEncoding;

  if aEncoding is TORawSingleByteEncoding then
  begin//RawByteString
    CopyOnWriteCheck;//we need COW check here because we modify buffer directly
    xByte := PByte(GetBuffer);
    for I := 0 to Length-1 do
    begin
      case xByte^ of
        Ord('A')..Ord('Z'): xByte^ := xByte^ + $20;
      end;
      Inc(xByte);
    end;
  end else
    SetString(SysUtils.LowerCase(GetString(aEncoding)), aEncoding);
end;

procedure TORawByteString.ToUpper(aEncoding: TEncoding);
var
  I: Integer;
  xByte: PByte;
begin
  if not Assigned(aEncoding) then
    aEncoding := GetDefaultEncoding;

  if aEncoding is TORawSingleByteEncoding then
  begin//RawByteString
    CopyOnWriteCheck;//we need COW check here because we modify buffer directly
    xByte := PByte(GetBuffer);
    for I := 0 to Length-1 do
    begin
      case xByte^ of
        Ord('a')..Ord('z'): xByte^ := xByte^ - $20;
      end;
      Inc(xByte);
    end;
  end else
    SetString(SysUtils.UpperCase(GetString(aEncoding)), aEncoding);
end;

{$IFDEF ORAW_IMPLICIT}
class operator TORawByteString.Implicit(const a: PORawByteChar): TORawByteString;
{$ELSE}
class operator TORawByteString.Explicit(const a: PORawByteChar): TORawByteString;
{$ENDIF}
var
  xC: PByte;
begin
  xC := PByte(a);
  while xC^ <> 0 do
    Inc(xC);

  Result.Length := 0;
  Result.Append(a^, NativeInt(xC)-NativeInt(a));
end;

{ TORawByteString.TORawByteStringCharEnumerator }

constructor TORawByteString.TORawByteStringCharEnumerator.Create(
  aBuffer: PORawByteString);
begin
  inherited Create;

  FIndex := -1;
  FString := aBuffer;
end;

function TORawByteString.TORawByteStringCharEnumerator.GetCurrent: TORawByteChar;
begin
  Result := FString^.GetChar(FIndex);
end;

function TORawByteString.TORawByteStringCharEnumerator.MoveNext: Boolean;
begin
  Result := FIndex < FString.Length - 1;
  if Result then
    Inc(FIndex);
end;

{ TORawSingleByteEncoding }

constructor TORawSingleByteEncoding.Create;
begin
  inherited;

  FIsSingleByte := True;
  FMaxCharSize := 1;
end;

destructor TORawSingleByteEncoding.Destroy;
begin
  if Self = FSavedInstance then
    FSavedInstance := nil;

  inherited;
end;

class destructor TORawSingleByteEncoding.Destroy;
begin
  //no need to initialize FSavedInstance in class constructor to nil
  //  (global variables are automatically initialized).
  //the FSavedInstance is set to nil in the destructor
  FSavedInstance.Free;

  inherited;
end;

function TORawSingleByteEncoding.GetByteCount(Chars: PChar;
  CharCount: Integer): Integer;
begin
  Result := CharCount;
end;

function TORawSingleByteEncoding.GetBytes(Chars: PChar; CharCount: Integer;
  Bytes: PByte; ByteCount: Integer): Integer;
var
  I: Integer;
  C: PWord;
begin
  Result := ByteCount;
  if Result > CharCount then
    Result := CharCount;

  if Result > 0 then
  begin
    C := PWord(Chars);
    for I := 0 to Result-1 do
    begin
      Bytes[I] := (C^ and $FF);
      Inc(C);
    end;
  end;
end;

function TORawSingleByteEncoding.GetCharCount(Bytes: PByte;
  ByteCount: Integer): Integer;
begin
  Result := ByteCount;
end;

function TORawSingleByteEncoding.GetChars(Bytes: PByte; ByteCount: Integer;
  Chars: PChar; CharCount: Integer): Integer;
var
  I: Integer;
  C: PWord;
begin
  Result := ByteCount;
  if Result > CharCount then
    Result := CharCount;

  if Result > 0 then
  begin
    C := PWord(Chars);
    for I := 0 to Result-1 do
    begin
      C^ := Bytes[I];
      Inc(C);
    end;
  end;
end;

function TORawSingleByteEncoding.GetMaxByteCount(CharCount: Integer): Integer;
begin
  Result := CharCount;
end;

function TORawSingleByteEncoding.GetMaxCharCount(ByteCount: Integer): Integer;
begin
  Result := ByteCount;
end;

function TORawSingleByteEncoding.GetPreamble: TBytes;
begin
  SetLength(Result, 0);
end;

class function TORawSingleByteEncoding.Use: TORawSingleByteEncoding;
begin
  if not Assigned(FSavedInstance) then
    FSavedInstance := TORawSingleByteEncoding.Create;

  Result := FSavedInstance;
end;

{ TORawByteChar }

class operator TORawByteChar.Equal(const a1, a2: TORawByteChar): Boolean;
begin
  Result := a1.FChar = a2.FChar;
end;

class operator TORawByteChar.Equal(const a1: TORawByteChar;
  const a2: Char): Boolean;
begin
  Result := a1.FChar = (Ord(a2) and $FF);
end;

class operator TORawByteChar.Equal(const a1: Char;
  const a2: TORawByteChar): Boolean;
begin
  Result := (Ord(a1) and $FF) = a2.FChar;
end;

{$IFDEF ORAW_IMPLICIT}
class operator TORawByteChar.Implicit(const a: Char): TORawByteChar;
{$ELSE}
class operator TORawByteChar.Explicit(const a: Char): TORawByteChar;
{$ENDIF}
begin
  Result.FChar := (Ord(a) and $FF);
end;

{$IFDEF ORAW_IMPLICIT}
class operator TORawByteChar.Implicit(const a: TORawByteChar): Char;
{$ELSE}
class operator TORawByteChar.Explicit(const a: TORawByteChar): Char;
{$ENDIF}
begin
  Result := Char(a.FChar);
end;

class operator TORawByteChar.GreaterThan(const a1, a2: TORawByteChar): Boolean;
begin
  Result := a1.FChar > a2.FChar;
end;

class operator TORawByteChar.GreaterThan(const a1: TORawByteChar;
  const a2: Char): Boolean;
begin
  Result := a1.FChar > (Ord(a2) and $FF);
end;

class operator TORawByteChar.GreaterThan(const a1: Char;
  const a2: TORawByteChar): Boolean;
begin
  Result := (Ord(a1) and $FF) > a2.FChar;
end;

class operator TORawByteChar.GreaterThanOrEqual(const a1: Char;
  const a2: TORawByteChar): Boolean;
begin
  Result := (Ord(a1) and $FF) >= a2.FChar;
end;

class operator TORawByteChar.GreaterThanOrEqual(const a1: TORawByteChar;
  const a2: Char): Boolean;
begin
  Result := a1.FChar >= (Ord(a2) and $FF);
end;

class operator TORawByteChar.GreaterThanOrEqual(const a1,
  a2: TORawByteChar): Boolean;
begin
  Result := a1.FChar >= a2.FChar;
end;

class operator TORawByteChar.LessThan(const a1: Char;
  const a2: TORawByteChar): Boolean;
begin
  Result := (Ord(a1) and $FF) < a2.FChar;
end;

class operator TORawByteChar.LessThan(const a1: TORawByteChar;
  const a2: Char): Boolean;
begin
  Result := a1.FChar < (Ord(a2) and $FF);
end;

class operator TORawByteChar.LessThan(const a1, a2: TORawByteChar): Boolean;
begin
  Result := a1.FChar < a2.FChar;
end;

class operator TORawByteChar.LessThanOrEqual(const a1,
  a2: TORawByteChar): Boolean;
begin
  Result := a1.FChar <= a2.FChar;
end;

class operator TORawByteChar.LessThanOrEqual(const a1: TORawByteChar;
  const a2: Char): Boolean;
begin
  Result := a1.FChar <= (Ord(a2) and $FF);
end;

class operator TORawByteChar.LessThanOrEqual(const a1: Char;
  const a2: TORawByteChar): Boolean;
begin
  Result := (Ord(a1) and $FF) <= a2.FChar;
end;

class operator TORawByteChar.NotEqual(const a1: TORawByteChar;
  const a2: Char): Boolean;
begin
  Result := a1.FChar = (Ord(a2) and $FF);
end;

class operator TORawByteChar.NotEqual(const a1: Char;
  const a2: TORawByteChar): Boolean;
begin
  Result := (Ord(a1) and $FF) = a2.FChar;
end;

function TORawByteChar.ToLower: TORawByteChar;
begin
  case FChar of
    Ord('A')..Ord('Z'): Result.FChar := FChar + $20;
  else
    Result := Self;
  end;
end;

function TORawByteChar.ToUpper: TORawByteChar;
begin
  case FChar of
    Ord('a')..Ord('z'): Result.FChar := FChar - $20;
  else
    Result := Self;
  end;
end;

class operator TORawByteChar.NotEqual(const a1, a2: TORawByteChar): Boolean;
begin
  Result := a1.FChar <> a2.FChar;
end;

{$IFDEF ORAW_IMPLICIT}
class operator TORawByteChar.Implicit(const a: Byte): TORawByteChar;
{$ELSE}
class operator TORawByteChar.Explicit(const a: Byte): TORawByteChar;
{$ENDIF}
begin
  Result.FChar := a;
end;

{$IFDEF ORAW_IMPLICIT}
class operator TORawByteChar.Implicit(const a: TORawByteChar): Byte;
{$ELSE}
class operator TORawByteChar.Explicit(const a: TORawByteChar): Byte;
{$ENDIF}
begin
  Result := a.FChar;
end;

{ TEncodingHelper }

class function TEncodingHelper.RawByte: TEncoding;
begin
  Result := TORawSingleByteEncoding.Use;
end;

end.
