{******************************************} { } { FastReport VCL } { Data Structures } { } { Copyright (c) 1998-2021 } { by Fast Reports Inc. } { } {******************************************} unit frxStorage; {$I frx.inc} interface uses Classes, {$IFNDEF FPC} Windows, {$ELSE} LCLType, LCLIntf, LCLProc, LazHelper, {$ENDIF} SysUtils, Graphics; type { Old Delphi versions don't declare these classes } ENotImplemented = class(Exception); ENotSupportedException = class(Exception); { Provides the TStream interface to a piece of a stream } TProxyStream = class(TStream) private FStream: TStream; FOffset: Longint; FPos: Longint; FSize: Longint; procedure Init(Stream: TStream; Offset, Size: Longint); function AdjustRange(var Len: Integer): Boolean; public constructor Create(Stream: TStream; Offset, Size: Longint); function Read(var Buffer; Count: Longint): Longint; override; function Write(const Buffer; Count: Longint): Longint; override; function Seek(Offset: Longint; Origin: Word): Longint; override; property Offset: Longint read FOffset; property BaseStream: TStream read FStream; end; { Provides additional functions for writing to an already existing stream. } TFmtStream = class(TStream) private FOutput: TStream; FOwn: Boolean; FFormatted: Boolean; FIndent: Integer; public constructor Create(Output: TStream; Own: Boolean); destructor Destroy; override; function Read(var Buffer; Count: Longint): Longint; override; function Write(const Buffer; Count: Longint): Longint; override; procedure PutsRawA(const s: AnsiString); overload; procedure PutsRaw(const s: string); overload; procedure PutsRawA(const Fmt: AnsiString; const Args: array of const); overload; procedure PutsRaw(const Fmt: string; const Args: array of const); overload; procedure PutsA(const s: AnsiString); overload; procedure Puts(const s: string = ''); overload; procedure PutsA(const Fmt: AnsiString; const Args: array of const); overload; procedure Puts(const Fmt: string; const Args: array of const); overload; procedure IncIndent(Step: Integer); property Formatted: Boolean read FFormatted write FFormatted; property Indent: Integer read FIndent write FIndent; end; { List of objects. This class assumes that it contains a list of TObject instances. When it's destroyed, it also destroys all contained objects. } TObjList = class(TList) procedure Clear; override; end; { Encodes an input stream and writes the result to an output stream. After sending the whole input stream to the encoder, it must be destroyed in order to force the encoder to write special ending bytes to the output stream. Example: ms := TMemoryStream.Create; s := 'data:image/jpeg;base64,'; ms.Write(s[1], Length(s)); with TBase64Encoder.Create(ms) do try Picture.SaveToStream(This) finally Free end; } TBase64Encoder = class(TStream) private FOutput: TStream; FCache, FUsed: Integer; FMap: array[0..63] of Byte; protected procedure InitMap; procedure Encode(a, n: Integer); procedure Finalise; public constructor Create(Output: TStream); destructor Destroy; override; function Read(var Buffer; Count: Longint): Longint; override; function Write(const Buffer; Count: Longint): Longint; override; function This: TBase64Encoder; end; { Performs simple hex encoding: each input byte is replaced with a 2-byte string containing the hexadecimal value of that byte. } THexEncoder = class(TStream) private FOutput: TStream; public constructor Create(Output: TStream); function Read(var Buffer; Count: Longint): Longint; override; function Write(const Buffer; Count: Longint): Longint; override; end; { Writes the end-of-line char after every specified number of bytes } TLineSplitter = class(TStream) private FSep: AnsiString; FOutput: TStream; FLength: Integer; FWritten: Integer; public constructor Create(Output: TStream; Length: Integer; Sep: AnsiString = #13#10); function Read(var Buffer; Count: Longint): Longint; override; function Write(const Buffer; Count: Longint): Longint; override; end; { Block stream. This class virtually divides all data into equally sized blocks, except for the last block that can has less size. } TBlockStream = class(TMemoryStream) private FBlockShift: LongInt; function GetBlocksCount: LongInt; function GetBlockData(i: LongInt): Pointer; function GetBlockSize(i: LongInt): LongInt; function GetCurrentBlock: LongInt; public constructor Create(BlockSizeShift: LongInt = 4); { Writes a specified count of bytes from a specified value and returns a pointer to the written data. } function Imm(Value, Count: LongInt): Pointer; { Fills a specified count of bytes with a specified value. } procedure Fill(Value: Byte; Count: LongInt); { Fills the remaining space of the current block with a specified value. } procedure EndBlock(Value: Byte); { The data of the stream is virtually split into equally sized blocks (except for the last block, that can have less size). } property BlocksCount: LongInt read GetBlocksCount; property BlockData[i: LongInt]: Pointer read GetBlockData; property BlockSize[i: LongInt]: LongInt read GetBlockSize; { Returns an index to a block where the next byte will be written. } property CurrentBlock: LongInt read GetCurrentBlock; property BlockShift: LongInt read FBlockShift; end; { Cached stream. This class is a layer on another (file)stream and implements the interface of a write-only stream. It uses an intermediate small cache in physical memory to avoid calling the underlying stream for writing small data blocks. } TCachedStream = class(TStream) private FStream: TStream; FDeleteOnDestroy: Boolean; FChunk: array of Byte; FUsed: Integer; function GetCacheSize: Integer; procedure SetCacheSize(Size: Integer); protected procedure FlushCache; public constructor Create(Stream: TStream; DeleteOnDestroy: Boolean); destructor Destroy; override; property CacheSize: Integer read GetCacheSize write SetCacheSize; function Read(var Buffer; Count: Longint): Longint; override; function Write(const Buffer; Count: Longint): Longint; override; function Seek(Offset: Longint; Origin: Word): Longint; override; end; { Pointer-to-Pointer map interface } TMap = class protected procedure SetItemExistence(Key: Pointer; b: Boolean); virtual; abstract; function ItemExists(Key: Pointer): Boolean; virtual; abstract; function GetItem(Key: Pointer): Pointer; virtual; abstract; procedure SetItem(Key: Pointer; Value: Pointer); virtual; abstract; public property Exists[Key: Pointer]: Boolean read ItemExists write SetItemExistence; property Items[Key: Pointer]: Pointer read GetItem write SetItem; default; end; TOrderedMap = class(TMap) public { Gets all key-value pairs. Keys will be sorted by ascending. Either Keys or Values can be nil - in this case the corresponding information will not be gathered. } procedure GetKeysAndValues(Keys, Values: TList); virtual; abstract; end; { B-tree is a balanced search tree which can insert, delete and find a key for O(log n) time. Its keys are always sorted, so it can return all key value pairs sorted by key. The order of a b-tree is the maximum number of subnodes each node can have. } TBTreeNode = class; TBTree = class(TOrderedMap) private FOrder: Integer; FRoot: TBTreeNode; function CreateNode(LNode: TBTreeNode = nil): TBTreeNode; function IsFull(Node: TBTreeNode): Boolean; procedure InsertKey(Node: TBTreeNode; i: Integer; Key, Value: Pointer; LNode, RNode: TBTreeNode); procedure SplitTree(Node: TBTreeNode; i: Integer); function FindIndex(Node: TBTreeNode; Key: Pointer): Integer; function FindKey(Key: Pointer; out KeyIndex: Integer): TBTreeNode; function GetHeight: Integer; function GetKeysCount: Integer; protected { Returns True if the key was inserted. Otherwise, the key was already there. } function Insert(Key: Pointer; Value: Pointer): Boolean; function ItemExists(Key: Pointer): Boolean; override; function GetItem(Key: Pointer): Pointer; override; procedure SetItem(Key: Pointer; Value: Pointer); override; procedure SetItemExistence(Key: Pointer; b: Boolean); override; property Root: TBTreeNode read FRoot; property Height: Integer read GetHeight; property KeysCount: Integer read GetKeysCount; property Order: Integer read FOrder; public constructor Create(Order: Integer = 4); destructor Destroy; override; procedure GetKeysAndValues(Keys, Values: TList); override; end; TBTreeNode = class private FLen: Integer; FKeys: array of Pointer; FRefs: array of TBTreeNode; FVals: array of Pointer; procedure SetLen(n: Integer); function GetKey(i: Integer): Pointer; function GetRef(i: Integer): TBTreeNode; function GetVal(i: Integer): Pointer; procedure SetKey(i: Integer; k: Pointer); procedure SetRef(i: Integer; r: TBTreeNode); procedure SetVal(i: Integer; v: Pointer); public constructor Create(MaxLen: Integer); property Len: Integer read FLen write SetLen; property Keys[i: Integer]: Pointer read GetKey write SetKey; property Refs[i: Integer]: TBTreeNode read GetRef write SetRef; property Vals[i: Integer]: Pointer read GetVal write SetVal; end; { AVL tree is a balanced search tree which can insert, delete and find a key for O(log n) time. Its keys are always sorted, so it can return all key value pairs sorted by key. AVL tree inserts new keys slower than B-tree, but performs lookup faster. } TAVLTreeNode = class; TAVLTree = class(TMap) private FRoot: TAVLTreeNode; function FindKey(Key: Pointer; out Node: TAVLTreeNode): Boolean; procedure Insert(Parent, Node: TAVLTreeNode; Key, Value: Pointer); procedure LiftChild(R, N: TAVLTreeNode; Leaf: Boolean); protected procedure SetItemExistence(Key: Pointer; b: Boolean); override; function ItemExists(Key: Pointer): Boolean; override; function GetItem(Key: Pointer): Pointer; override; procedure SetItem(Key: Pointer; Value: Pointer); override; property Root: TAVLTreeNode read FRoot; public destructor Destroy; override; end; TAVLTreeNode = class private FKey, FValue: Pointer; FHeight: Byte; FLeafs: array[Boolean] of TAVLTreeNode; function GetChild(Leaf: Boolean): TAVLTreeNode; procedure SetChild(Leaf: Boolean; Node: TAVLTreeNode); function GetHeight: Byte; public constructor Create(Key, Value: Pointer); destructor Destroy; override; procedure UpdateHeight; property Key: Pointer read FKey; property Value: Pointer read FValue write FValue; property Height: Byte read GetHeight write FHeight; property Child[Leaf: Boolean]: TAVLTreeNode read GetChild write SetChild; default; end; { Hash table This is a base hash table class } THashTable = class protected procedure SetValueInternal(Key:Integer; Value: Pointer); public { Returns a value by a specified key. Returns zero if the value doesn't exist. } function GetValue(Key: Integer): Pointer; virtual; abstract; { Sets a value corresponding to a specified key. If the value for this key already exists, the routine doesn't change the previous value and returns it. If the value doesn't exist, the routine changes the value for the key and returns zero. } function SetValue(Key: Integer; Value: Pointer): Pointer; virtual; abstract; { Performs a self test. Arguments: • RS - randomly choosen value • n - a count of iterations } function SelfTest(RS, n: Integer): Boolean; property Items[Key: Integer]: Pointer read GetValue write SetValueInternal; default; end; { Hash table This is a "classic" implementation of a hash table. } TLhtEntry = packed record Next: Pointer; Key: Integer; Data: Pointer; end; PLhtEntry = ^TLhtEntry; TListHashTable = class(THashTable) private FBushes: Pointer; // List of pointers to TLhtEntry FHeap: THandle; function CreateEntry(Key: Integer): PLhtEntry; public { Returns a value by a specified key. Returns zero if the value doesn't exist. } function GetValue(Key: Integer): Pointer; override; { Sets a value corresponding to a specified key. } function SetValue(Key: Integer; Value: Pointer): Pointer; override; constructor Create; destructor Destroy; override; end; TListBaseHashTable = class public ListKeys: TStringList; constructor Create; destructor Destroy; override; end; TListStringHashTable = class(TListBaseHashTable) public ListValues :TStringList; function GetValue(Key: String): String; procedure SetValue(Key: String; Value: String); procedure Clear(); constructor Create; destructor Destroy; override; end; TListStreamHashTable = class(TListBaseHashTable) public ListValues :array of TStream; function GetValue(Key: String): TStream; procedure SetValue(Key: String; Value: TStream); procedure Clear(); constructor Create; destructor Destroy; override; end; TfrxDataHashMap = class private ListKeys: TStringList; ListValues :TList; public constructor Create; destructor Destroy; override; function Count: Integer; procedure Clear; procedure AddPicture(md5s: String); overload; procedure AddPicture(md5s: String; i1: Integer); overload; function FindPicture(md5s: String): Integer; function GetData(ind: Integer): Integer; end; TPictureHashMapResultType = (phmIndex, phmData); TFindOrAddGraphic = function (g: TGraphic; ResultType: TPictureHashMapResultType; IntData: Integer = -1): Integer of object; TGetIsLastNew = function: Boolean of object; TfrxPictureHashMap = class private //For Real FfrxDataHashMap: TfrxDataHashMap; FIsLastNew: Boolean; //For Fake FCount: Integer; FRealHash: Boolean; FFindOrAddGraphic: TFindOrAddGraphic; FGetIsLastNew: TGetIsLastNew; function RealFindOrAddGraphic(g: TGraphic; ResultType: TPictureHashMapResultType; IntData: Integer = -1): Integer; function RealGetIsLastNew: Boolean; function FakeFindOrAddGraphic(g: TGraphic; ResultType: TPictureHashMapResultType; IntData: Integer = -1): Integer; function FakeGetIsLastNew: Boolean; procedure SetRealHash(RealHash: Boolean); public constructor Create(RealHash: Boolean); destructor Destroy; override; property FindOrAddGraphic: TFindOrAddGraphic read FFindOrAddGraphic; property IsLastNew: TGetIsLastNew read FGetIsLastNew; property RealHash: Boolean read FRealHash write SetRealHash; end; { Cache. This class provides caching features for storing a big list of objects. The cache provides a transparent access to objects by indexes. There're two requirements that objects must satisfy if they are kept in the cache: • Any object can save itself to a stream and can load itself from a stream • The cache must not contain two identical references at a time } TLcWriteObj = procedure(Stream: TStream; Obj: TObject); TLcReadObj = function(Stream: TStream): TObject; TLcObject = record Offset: Int64; Size: Int64; end; TLcCreateParams = record CacheStream: TStream; ObjectsStream: TStream; QueueSize: Integer; end; TLcLoadedObject = record Reference: TObject; Index: Integer; end; TListCache = class private { TListCache can work as a usual list without caching. In this mode the objects are stored in this list. } FObjects: TObjList; { This stream contains a sequence of serialized objects. Normally, this stream is a disk file. } FCacheStream: TStream; { This stream contains TLcObject structures. The i-th structure describes the object at the i-th index. • TLcObject.Offset The index to the first byte in FCacheStream where the object is stored. • TLcObject.Size The number of bytes occupied in FCacheStream by the stored object. } FObjectsStream: TStream; { This list contains indexes to recently loaded objects. The first item of this list refers to the most recent object. Access to this queue must be performed via PushObject. } FCacheQueue: array of TLcLoadedObject; { If enabled, objects that are unloaded will be written to the cache stream even if they were already written to the stream in past. } FUpdate: Boolean; { Protects objects from overwriting } FProtect: Boolean; { If False, unloaded objects are written to the end of the cache stream. This greatly increases size of the stream, but this has to be done for objects with dynamic size. } FStaticSize: Boolean; { If these names of files are specified, then the destructor must close the two streams and delete the files. } FCacheFile: string; FObjFile: string; protected function IsCaching: Boolean; { Calls destructor for all objects in the queue and then makes the queue empty. } procedure ClearQueue; { Access to list of objects must be peformed via two routines: • GetObject( Index ) Finds a specified object. If the object is presented in physical memory, its returned. If the object is not in physical memory its loaded from a cache stream and returned. • SetObject( Index, Object ) Writes a specified object to a specified location. Maybe, the routine moves some objects from physical memory to a cache stream. } function GetObject(Index: Integer): TObject; procedure SetObject(Index: Integer; Obj: TObject); { Adds a specified object to the head of the queue. If the queue is full, then an object in the tail of the queue is unloaded to the cache stream. } procedure PushObject(Obj: TObject; Index: Integer); procedure UnloadObject(Obj: TObject; Index: Integer); function GetObjectsCount: Integer; procedure SetObjectsCount(NewCount: Integer); { This routine is called by constructors for initialization } procedure Initialize(Params: TLcCreateParams); public WriteObj: TLcWriteObj; ReadObj: TLcReadObj; { This constructor creates TListCache that works as a usual TObjList without caching. } constructor Create; overload; { Creates a cache. Arguments: • Params.CacheStream A stream that will contain temporarily unloaded objects. • Params.QueueSize A number of objects that can be presented in physical memory at a time. • Params.ObjectsStream This stream will contain a list of TLcObject structures. The number of these structures equals the number of objects. } constructor Create(Params: TLcCreateParams); overload; { This constructor creates two files with the specified names and uses them as CacheStream and ObjectsStream. When the destructor is called, these files are closed and deleted. } constructor Create(CacheFile, ObjFile: string; QueueSize: Integer = 4); overload; destructor Destroy; override; procedure Clear; procedure Exchange(Index1, Index2: Integer); function First: TObject; function Last: TObject; function Add(Obj: TObject): Integer; { Access to objects by indexes. When a caller attempts to write an object at a non existing index, the internal list is extended and the object is correctly written. When a caller attempts to overwrite an existing object, and exception is raised. } property Objects[Index: Integer]: TObject read GetObject write SetObject; default; { Returns a number of references to objects. Some references can be zero. } property Count: Integer read GetObjectsCount write SetObjectsCount; { The cache keeps only a few objects in memory at a time. When a caller attempts to load an object that is not in memory, it's loaded into memory. After that the cache may have to unload another object back to a cache stream. The object that is going to be unloaded can differ from its copy in the cache stream. In this case the object must be written to the cache stream again. This property controls the behaviour of the cache in this situation: • Option enabled The object that's goigng to be unloaded will be written to the cache stream, even if it already has a copy in the cache stream. This option must be enabled if the object has been changed since a time when it was written to the cache stream. • Option disabled The object that's going to be unloaded will be removed from memory and will not be written to the cache stream. This option must be disabled if the object has not been changed since the last time when it was written to the cache stream. } property UpdateWhenUnload: Boolean read FUpdate write FUpdate; {default True} { When a caller attempts to put an object to a specified location, like as follows: Objects[777] := Obj it can turn out that the specified location is already occupied by another object. When this option is enabled, the cache will raise an exception for these cases. } property Protect: Boolean read FProtect write FProtect; {default False} { If WriteObj routine can write different number of bytes for the same object, then this property must be set to False. } property StaticSize: Boolean read FStaticSize write FStaticSize; {default False} end; { Data block } TDataBlock = class public Data: Pointer; Size: Integer; constructor Create(Size: Integer; Zero: Boolean = True); destructor Destroy; override; end; { This function returns a negative integer when x < y and returns a non-negative integer in other cases. } TSimpleCompareFunction = function(const x, y: Pointer): Integer; { Array interface } TfrxArray = class protected function GetZero: Boolean; virtual; abstract; procedure SetZero(f: Boolean); virtual; abstract; function GetItemSize: Integer; virtual; abstract; function GetCount: Integer; virtual; abstract; procedure SetCount(Value: Integer); virtual; abstract; function GetItemData(Index: Integer): Pointer; virtual; abstract; procedure GetItem(Index: Integer; var Item); procedure SetItem(Index: Integer; const Item); procedure Insert(Index: Integer; const Value); procedure Append(const Value); procedure VerifyIndex(Index: Integer); procedure ResetItems(Min, Max: Integer); property ItemData[Index: Integer]: Pointer read GetItemData; property ItemSize: Integer read GetItemSize; public destructor Destroy; override; procedure Clear; virtual; procedure Exchange(Index1, Index2: Integer); virtual; procedure Delete(Index: Integer); virtual; procedure Sort(Compare: TSimpleCompareFunction; Min, Max: Integer); virtual; property Count: Integer read GetCount write SetCount; { If set, all newly created items are filled with zeros. If not set, bits of all new blocks are set to 1. } property Zero: Boolean read GetZero write SetZero; end; TfrxBaseArray = class(TfrxArray) protected FCapacity: Integer; FSorted: Boolean; FCount: Integer; procedure Grow; virtual; procedure SetCount(Value: Integer); override; function GetCount: Integer; override; procedure SetNewCapacity(Value: Integer); virtual; public procedure Clear; override; end; { Cached array of equal sized structures } TfrxCachedArray = class(TfrxArray) private FCount: Integer; // number of items FBlock: Integer; // number of items in a block FStorage: TListCache; FItemSize: Integer; FZero: Boolean; function CreateBlock: TDataBlock; procedure Initialize; protected function GetZero: Boolean; override; procedure SetZero(f: Boolean); override; function GetItemSize: Integer; override; function GetCount: Integer; override; procedure SetCount(Value: Integer); override; function GetItemData(Index: Integer): Pointer; override; public { TCachedArray is based on TListCache, so its constructor has similar arguments. • DataStream This stream is used as a temporary storage for array elements. The size of the stream is quite equal to the size of the array. This stream is created and deleted by a client, this class only uses it and doesn't delete. • ServStream This stream is used for some service information required by TListCache. The size of this stream is rather small. It's created and deleted by a client, this class doesn't delete it. • DataFile This is the name of a file that will be created and used as DataStream. The destructor of this class deletes the created file. • ServFile This is the name of a file that will be created and used as ServStream. The destructor of this class deletes the created file. • Block All the elements of the array will be grouped into blocks of this size. Any block can either be in physical memory or in the DataStream. This value means the number of bytes in each block. } constructor Create(DataStream, ServStream: TStream; Block, ItemSize: Integer); overload; constructor Create(DataFile, ServFile: string; Block, ItemSize: Integer); overload; destructor Destroy; override; procedure Sort(Compare: TSimpleCompareFunction; Min, Max: Integer); override; end; { Abstract array of Integers } TfrxIntArrayBase = class(TfrxBaseArray) protected function GetItem(Index: Integer): Integer; virtual; abstract; procedure SetItem(Index: Integer; Value: Integer); virtual; abstract; function GetItemSize: Integer; override; public procedure Insert(Index: Integer; Value: Integer); procedure Append(Value: Integer); property Items[Index: Integer]: Integer read GetItem write SetItem; default; end; { Array of Integers } TfrxIntArray = class(TfrxIntArrayBase) private FArray: array of Integer; FZero: Boolean; protected function GetZero: Boolean; override; procedure SetZero(f: Boolean); override; function GetItem(Index: Integer): Integer; override; procedure SetItem(Index: Integer; Value: Integer); override; function GetItemData(Index: Integer): Pointer; override; procedure SetNewCapacity(Value: Integer); override; end; { Cached array of Integers } TfrxCachedIntArray = class(TfrxIntArrayBase) private FArray: TfrxCachedArray; protected function GetZero: Boolean; override; procedure SetZero(f: Boolean); override; function GetItem(Index: Integer): Integer; override; procedure SetItem(Index: Integer; Value: Integer); override; function GetCount: Integer; override; procedure SetCount(Value: Integer); override; function GetItemData(Index: Integer): Pointer; override; public constructor Create(DataStream, ServStream: TStream; Block: Integer); overload; constructor Create(DataFile, ServFile: string; Block: Integer); overload; destructor Destroy; override; end; { Abstract array of Integers } TfrxExtArrayBase = class(TfrxBaseArray) protected function GetItem(Index: Integer): Extended; virtual; abstract; procedure SetItem(Index: Integer; Value: Extended); virtual; abstract; function GetItemSize: Integer; override; public procedure Insert(Index: Integer; Value: Extended); procedure Append(Value: Extended); property Items[Index: Integer]: Extended read GetItem write SetItem; default; end; { Array of Integers } TfrxExtArray = class(TfrxExtArrayBase) private FArray: array of Extended; FZero: Boolean; protected function GetZero: Boolean; override; procedure SetZero(f: Boolean); override; function GetItem(Index: Integer): Extended; override; procedure SetItem(Index: Integer; Value: Extended); override; function GetItemData(Index: Integer): Pointer; override; procedure SetNewCapacity(Value: Integer); override; end; TfrxExtendedObjectList = class(TfrxExtArray) private FObjectList: array of TObject; FFreeObjects: Boolean; function GetObject(Index: Integer): TObject; procedure SetObject(Index: Integer; const Value: TObject); protected procedure SetNewCapacity(Value: Integer); override; public procedure AppendObject(Value: Extended; aObject: TObject); procedure Clear; override; procedure InsertObject(Index: Integer; Value: Extended; aObject: TObject); procedure Exchange(Index1, Index2: Integer); override; procedure Delete(Index: Integer); override; procedure SortList; property Objects[Index: Integer]: TObject read GetObject write SetObject; property FreeObjects: Boolean read FFreeObjects write FFreeObjects; end; { Cached array of Integers } TfrxCachedExtArray = class(TfrxExtArrayBase) private FArray: TfrxCachedArray; protected function GetZero: Boolean; override; procedure SetZero(f: Boolean); override; function GetItem(Index: Integer): Extended; override; procedure SetItem(Index: Integer; Value: Extended); override; function GetCount: Integer; override; procedure SetCount(Value: Integer); override; function GetItemData(Index: Integer): Pointer; override; public constructor Create(DataStream, ServStream: TStream; Block: Integer); overload; constructor Create(DataFile, ServFile: string; Block: Integer); overload; destructor Destroy; override; end; { This class writes and reads various data types to an arbitrary stream } TStreamRW = class private FStream: TStream; FSecure: Boolean; function GetBoolType: Integer; function GetIntType: Integer; function GetExtType: Integer; function GetStrType: Integer; procedure WriteType(t: Integer); procedure ReadType(t: Integer); public { If Secure = True then any value will be written right after a 4-byte code that describe its type. When a value is to be read, the leading 4-byte code is read and compared with the type of the value and if the two 4-byte codes don't match, an exception is raised. } constructor Create(Stream: TStream; Secure: Boolean = False); procedure WriteBool(x: Boolean); procedure WriteInt(x: Integer); procedure WriteExt(x: Extended); procedure WriteStr(x: WideString); function ReadBool: Boolean; function ReadInt: Integer; function ReadExt: Extended; function ReadStr: WideString; end; { Array of bits } TBitArray = class private FLength: Integer; FData: array of Byte; function GetBit(Index: Integer): Boolean; procedure SetBit(Index: Integer; Value: Boolean); procedure SetBitsLength(NewLength: Integer); procedure Trunc; public function Clone: TBitArray; procedure ResetBits; // make all bits equal 0 procedure SetBits; // make all bits equal 1 procedure SaveToStream(Stream: TStream); function GetNumOfSetBits: Integer; function GetRightmostBitIndex: Integer; procedure BitOr(Src: TBitArray); // Self := Self or Src procedure BitAnd(Src: TBitArray); // Self := Self and Src procedure BitXor(Src: TBitArray); // Self := Self xor Src property Bits[Index: Integer]: Boolean read GetBit write SetBit; default; property Length: Integer read FLength write SetBitsLength; end; { Finds an entry with a specified key. If there's no such an entry, the routine returns the last entry in the list. } function LhtFindEntry(e: PLhtEntry; Key: Integer): PLhtEntry; cdecl; { Get/set an entry by its index. } function LhtGetEntry(Base: Pointer; Index: Integer): PLhtEntry; cdecl; procedure LhtSetEntry(Base: Pointer; Index: Integer; Entry: PLhtEntry); cdecl; { Serializing and deserializing routines for TDataBlock } function DbRead(Stream: TStream): TObject; procedure DbWrite(Stream: TStream; Block: TObject); implementation uses RTLConsts, Math, frxmd5; function GraphicToMD5(aGraphic: TGraphic): String; var mem: TMemoryStream; begin mem := TMemoryStream.Create; try aGraphic.SaveToStream(mem); Result := String(MD5Stream(mem)); finally mem.Free; end; end; var NumSetBits: array[0..255] of Byte; // NumSetBits[b] = the number of "1" bits in b type {$IFDEF DELPHI16} frxInteger = NativeInt; frxCardinal = UInt64; {$ELSE} frxInteger = {$IFDEF FPC}PtrInt{$ELSE}Integer{$ENDIF}; frxCardinal = {$IFDEF FPC}Cardinal{$ELSE}Cardinal{$ENDIF}; {$ENDIF} procedure InitNumSetBits; function GetNSB(b: Byte): Byte; begin Result := 0; while b > 0 do begin Inc(Result); b := b and (b - 1); end; end; var b: Byte; begin for b := 0 to 255 do NumSetBits[b] := GetNSB(b) end; procedure LhtSetEntry(Base: Pointer; Index: Integer; Entry: PLhtEntry); begin {$IFDEF WIN64} Move(Entry, Pointer(Int64(Base) + Index*SizeOf(PLhtEntry))^, SizeOf(PLhtEntry)); {$ELSE} Move(Entry, Pointer(Integer(Base) + Index*SizeOf(PLhtEntry))^, SizeOf(PLhtEntry)); {$ENDIF} end; function LhtGetEntry(Base: Pointer; Index: Integer): PLhtEntry; begin {$IFDEF WIN64} Move(Pointer(Int64(Base) + Index*SizeOf(PLhtEntry))^, Result, SizeOf(PLhtEntry)); {$ELSE} Move(Pointer(Integer(Base) + Index*SizeOf(PLhtEntry))^, Result, SizeOf(PLhtEntry)); {$ENDIF} end; function LhtFindEntry(e: PLhtEntry; Key: Integer): PLhtEntry; begin while (e <> nil) and (e^.Key <> Key) do e := e^.Next; Result := e; end; function AllocPage: Pointer; begin Result := VirtualAlloc(nil, 4096, MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE); end; function DeallocPage(Page: POinter): Boolean; begin Result := VirtualFree(Page, 0, MEM_RELEASE); end; procedure CheckBounds(i, Min, Max: LongInt); begin if (i < Min) or (i > Max) then raise Exception.CreateFmt('%d is out of bounds [%d, %d]', [i, Min, Max]) end; { TObjList } procedure TObjList.Clear; var i: LongInt; begin for i := 0 to Count - 1 do TObject(Items[i]).Free; inherited; end; { TBlockStream } constructor TBlockStream.Create(BlockSizeShift: LongInt); begin inherited Create; CheckBounds(BlockSizeShift, 1, 12); FBlockShift := BlockSizeShift; end; function TBlockStream.GetCurrentBlock: LongInt; begin Result := Size shr FBlockShift; end; procedure TBlockStream.EndBlock(Value: Byte); var n, m: LongInt; begin n := 1 shl FBlockShift; m := Size and (n - 1); if m > 0 then Fill(Value, n - m); end; procedure TBlockStream.Fill(Value: Byte; Count: LongInt); begin CheckBounds(Count, 1, MaxLongint); { todo: an efficient implementation can be done using a buffer in the thread stack. } while Count > 0 do begin Write(Value, 1); Dec(Count); end; end; function TBlockStream.GetBlocksCount: LongInt; begin Result := Size shr FBlockShift; if Size and (1 shl FBlockShift - 1) <> 0 then Inc(Result); end; function TBlockStream.GetBlockData(i: LongInt): Pointer; begin CheckBounds(i, 0, BlocksCount - 1); Result := Pointer(NativeInt(Memory) + i shl FBlockShift); end; function TBlockStream.GetBlockSize(i: LongInt): LongInt; begin CheckBounds(i, 0, BlocksCount - 1); Result := Size - i shl FBlockShift; if Result < 1 shl FBlockShift then Exit; Result := 1 shl FBlockShift; end; function TBlockStream.Imm(Value, Count: LongInt): Pointer; begin CheckBounds(Count, 1, SizeOf(Value)); Write(Value, Count); Result := Pointer(NativeInt(Memory) + Size - Count); end; { THashTable } function THashTable.SelfTest(RS, n: Integer): Boolean; var i: Integer; x: Pointer; begin RandSeed := RS; for i := 1 to n do begin x := Pointer(Random($7fffffff)); Items[Integer(x)] := x; if Items[Integer(x)] <> x then begin Result := False; Exit; end; end; Result := True; end; procedure THashTable.SetValueInternal(Key:Integer; Value: Pointer); begin SetValue(Key, Value); end; { TListHashTable } constructor TListHashTable.Create; begin {$IFDEF WIN64} FBushes := VirtualAlloc(nil, $10000 * sizeof(NativeInt), MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE); {$ELSE} FBushes := VirtualAlloc(nil, $10000 * sizeof(Integer), MEM_COMMIT or MEM_RESERVE, PAGE_READWRITE); {$ENDIF} FHeap := HeapCreate(5{HEAP_GENERATE_EXCEPTIONS or HEAP_NO_SERIALIZE}, 0, 0); if FBushes = nil then raise Exception.Create('Failed to allocate memory'); end; destructor TListHashTable.Destroy; begin VirtualFree(FBushes, 0, MEM_RELEASE); HeapDestroy(FHeap); end; function TListHashTable.CreateEntry(Key: Integer): PLhtEntry; begin Result := HeapAlloc(FHeap, 0, SizeOf(TLhtEntry)); Result^.Key := Key; Result^.Next := nil; end; function TListHashTable.GetValue(Key: Integer): Pointer; var e: PLhtEntry; begin e := LhtGetEntry(FBushes, Key and $ffff); e := LhtFindEntry(e, Key); if (e = nil) or (e^.Key <> Key) then Result := nil else Result := e^.Data; end; function TListHashTable.SetValue(Key: Integer; Value: Pointer): Pointer; var e, e2: PLhtEntry; begin e := LhtGetEntry(FBushes, Key and $ffff); e := LhtFindEntry(e, Key); if (e <> nil) and (e^.Key = Key) then begin Result := e^.Data; Exit; end; e2 := CreateEntry(Key); e2^.Data := Pointer(Value); if e = nil then LhtSetEntry(FBushes, Key and $ffff, e2) else e^.Next := e2; Result := nil; end; { TListStringHashTable } constructor TListBaseHashTable.Create; begin ListKeys := TStringList.Create(); end; destructor TListBaseHashTable.Destroy; begin ListKeys.Free(); end; { TListStringHashTable } function TListStringHashTable.GetValue(Key: String): String; var i: Integer; begin i := ListKeys.IndexOf(Key); if (i = -1) then result := '' else result := ListValues[i]; end; procedure TListStringHashTable.SetValue(Key: String; Value: String); begin if (GetValue(Key) = '') then begin ListKeys.Add(Key); ListValues.Add(Value); end else ListValues[ListKeys.IndexOf(Key)] := Value; end; procedure TListStringHashTable.Clear(); begin ListKeys.Clear(); ListValues.Clear(); end; constructor TListStringHashTable.Create; begin Inherited; ListValues := TStringList.Create(); end; destructor TListStringHashTable.Destroy; begin Inherited; ListValues.Free(); end; { TListStreamHashTable } function TListStreamHashTable.GetValue(Key: String): TStream; var i: Integer; begin i := ListKeys.IndexOf(Key); if (i = -1) then result := nil else result := ListValues[i]; end; procedure TListStreamHashTable.SetValue(Key: String; Value: TStream); begin if (GetValue(Key) = nil) then begin ListKeys.Add(Key); SetLength(ListValues, ListKeys.Count); ListValues[ListKeys.Count - 1] := Value; end else ListValues[ListKeys.IndexOf(Key)] := Value; end; procedure TListStreamHashTable.Clear(); var i: Integer; begin ListKeys.Clear(); for i := 0 to Length(ListValues) -1 do ListValues[i].Free(); SetLength(ListValues, 0); end; constructor TListStreamHashTable.Create; begin Inherited; end; destructor TListStreamHashTable.Destroy; begin Clear(); Inherited; end; { TfrxDataHashMap } constructor TfrxDataHashMap.Create; begin ListKeys := TStringList.Create; ListValues := TList.Create; end; destructor TfrxDataHashMap.Destroy; begin Clear; ListKeys.Free; ListValues.Free; inherited; end; function TfrxDataHashMap.Count: Integer; begin Result := ListKeys.Count; end; procedure TfrxDataHashMap.Clear; begin ListKeys.Clear(); ListValues.Clear(); end; procedure TfrxDataHashMap.AddPicture(md5s: String); begin AddPicture(md5s, -1); end; procedure TfrxDataHashMap.AddPicture(md5s: String; i1: Integer); var i: Integer; begin i := FindPicture(md5s); if (i = -1) then begin ListKeys.Add(md5s); ListValues.Add(Pointer(i1)); end; end; function TfrxDataHashMap.FindPicture(md5s: String): Integer; begin Result := ListKeys.IndexOf(md5s); end; function TfrxDataHashMap.GetData(ind: Integer): Integer; begin Result := -1; if (ind < ListValues.Count) then Result := Integer(ListValues[ind]); end; { TfrxPictureHashMap } constructor TfrxPictureHashMap.Create(RealHash: Boolean); begin SetRealHash(RealHash); end; destructor TfrxPictureHashMap.Destroy; begin FreeAndNil(FfrxDataHashMap); inherited; end; procedure TfrxPictureHashMap.SetRealHash(RealHash: Boolean); begin if RealHash and (FfrxDataHashMap = nil) then begin FfrxDataHashMap := TfrxDataHashMap.Create; FFindOrAddGraphic := RealFindOrAddGraphic; FGetIsLastNew := RealGetIsLastNew; end else begin FreeAndNil(FfrxDataHashMap); FFindOrAddGraphic := FakeFindOrAddGraphic; FGetIsLastNew := FakeGetIsLastNew; end; end; function TfrxPictureHashMap.RealFindOrAddGraphic(g: TGraphic; ResultType: TPictureHashMapResultType; IntData: Integer = -1): Integer; var md5s: String; IndexImg: Integer; begin md5s := GraphicToMD5(g); IndexImg := FfrxDataHashMap.FindPicture(md5s); FIsLastNew := (IndexImg = -1); if (FIsLastNew) then begin if (ResultType = phmIndex) then Result := FfrxDataHashMap.Count else Result := IntData; FfrxDataHashMap.AddPicture(md5s, IntData); end else begin if (ResultType = phmIndex) then Result := IndexImg else Result := FfrxDataHashMap.GetData(IndexImg); end; end; function TfrxPictureHashMap.RealGetIsLastNew: Boolean; begin Result := FIsLastNew; end; function TfrxPictureHashMap.FakeFindOrAddGraphic(g: TGraphic; ResultType: TPictureHashMapResultType; IntData: Integer = -1): Integer; begin Result := FCount; FCount := FCount + 1; end; function TfrxPictureHashMap.FakeGetIsLastNew: Boolean; begin Result := True; end; { TListCache } constructor TListCache.Create; begin FObjects := TObjList.Create; end; constructor TListCache.Create(Params: TLcCreateParams); begin Initialize(Params); end; constructor TListCache.Create(CacheFile, ObjFile: string; QueueSize: Integer); var p: TLcCreateParams; begin FCacheFile := CacheFile; FObjFile := ObjFile; p.CacheStream := TFileStream.Create(CacheFile, fmCreate); p.ObjectsStream := TFileStream.Create(ObjFile, fmCreate); p.QueueSize := QueueSize; Initialize(p); end; destructor TListCache.Destroy; begin if FObjFile <> '' then begin FObjectsStream.Free; DeleteFile(FObjFile); end; if FCacheFile <> '' then begin FCacheStream.Free; DeleteFile(FCacheFile); end; FObjects.Free; ClearQueue; end; procedure TListCache.Initialize(Params: TLcCreateParams); var i: Integer; begin if Params.CacheStream = Params.ObjectsStream then raise Exception.Create('Caching stream and objects stream must be different streams'); FUpdate := True; FCacheStream := Params.CacheStream; FObjectsStream := Params.ObjectsStream; SetLength(FCacheQueue, Params.QueueSize); for i := 0 to Length(FCacheQueue) - 1 do FCacheQueue[i].Reference := nil; end; procedure TListCache.Clear; begin if not IsCaching then begin FObjects.Clear; Exit; end; FCacheStream.Size := 0; FObjectsStream.Size := 0; ClearQueue; end; procedure TListCache.ClearQueue; var i: Integer; begin for i := 0 to Length(FCacheQueue) - 1 do with FCacheQueue[i] do begin Reference.Free; Reference := nil; end; end; procedure TListCache.Exchange(Index1, Index2: Integer); var i, j: Integer; r1, r2: TLcObject; begin if not IsCaching then begin FObjects.Exchange(Index1, Index2); Exit; end; with FObjectsStream do begin Seek(Index1 * SizeOf(TLcObject), soFromBeginning); Read(r1, SizeOf(TLcObject)); Seek(Index2 * SizeOf(TLcObject), soFromBeginning); Read(r2, SizeOf(TLcObject)); Seek(Index2 * SizeOf(TLcObject), soFromBeginning); Write(r1, SizeOf(TLcObject)); Seek(Index1 * SizeOf(TLcObject), soFromBeginning); Write(r2, SizeOf(TLcObject)); end; for i := 0 to Length(FCacheQueue) - 1 do begin j := FCacheQueue[i].Index; if j = Index1 then FCacheQueue[i].Index := Index2; if j = Index2 then FCacheQueue[i].Index := Index1; end; end; function TListCache.First: TObject; begin Result := Objects[0]; end; function TListCache.Last: TObject; begin Result := Objects[Count - 1]; end; function TListCache.Add(Obj: TObject): Integer; begin Result := Count; Objects[Count] := Obj; end; function TListCache.IsCaching: Boolean; begin Result := FCacheStream <> nil; end; function TListCache.GetObjectsCount: Integer; begin if IsCaching then Result := FObjectsStream.Size div SizeOf(TLcObject) else Result := FObjects.Count; end; procedure TListCache.SetObjectsCount(NewCount: Integer); var n, i: Integer; begin n := Count; { The case when the new size is greater than the current } if NewCount > n then begin SetObject(NewCount - 1, nil); Exit; end; { If the new size is smaller, remove the tail items from the queue } for i := 0 to Length(FCacheQueue) - 1 do with FCacheQueue[i] do if Index >= NewCount then begin Reference.Free; Reference := nil; end; { Remove descriptions of the tail items } FObjectsStream.Size := NewCount * SizeOf(TLcObject); end; function TListCache.GetObject(Index: Integer): TObject; var i: Integer; r: TLcLoadedObject; x: TLcObject; begin if (Index < 0) or (Index >= Count) then begin Result := nil; Exit; end; if not IsCaching then begin Result := FObjects[Index]; Exit; end; { Check whether the object is in memory } for i := 0 to Length(FCacheQueue) - 1 do begin r := FCacheQueue[i]; if (r.Index = Index) and (r.Reference <> nil) then begin Result := r.Reference; Exit; end; end; { If the required object is not in memory, load it from the cache stream. } FObjectsStream.Seek(Index * SizeOf(TLcObject), soFromBeginning); FObjectsStream.Read(x, SizeOf(TLcObject)); { Some objects stored by a client via SetObject can be nil. For these objects zero TLcObject entry corresponds. } if x.Size = 0 then begin Result := nil; Exit; end; { If the object is not nil, load it. } FCacheStream.Seek(x.Offset, soFromBeginning); Result := ReadObj(FCacheStream); { This exception occurs when an object was stored to the cache stream some time before and now it's been read, but the reading routine reads another number of bytes that the writing routine wrote. This exception indicates that the read object is possibly garbage. } if FCacheStream.Position <> x.Offset + x.Size then raise Exception.Create('The read object does not match the written object'); { Put the loaded object to the queue of objects which are located in the physical memory. } PushObject(Result, Index); end; procedure TListCache.SetObject(Index: Integer; Obj: TObject); var i: Integer; x: TLcObject; begin if Index < 0 then raise Exception.Create('Cannot store an object an a negative index'); if not IsCaching then begin if Index >= Count then FObjects.Count := Index + 1; FObjects[Index] := Obj; Exit; end; { Prevent overwriting of objects } if FProtect and (Index < Count) then begin FObjectsStream.Seek(Index * SizeOf(TLcObject), soFromBeginning); FObjectsStream.Read(x, SizeOf(TLcObject)); if x.Size > 0 then raise Exception.Create('Attempt to overwrite an existing object'); end; { If the index is out of bounds, expand the storage of objects } if Index >= Count then begin i := Index - Count + 1; ZeroMemory(@x, SizeOf(x)); FObjectsStream.Seek(0, soFromEnd); while i > 0 do begin FObjectsStream.Write(x, SizeOf(x)); Dec(i); end; end; { Notify the system that the object is in memory } if Obj <> nil then PushObject(Obj, Index); end; procedure TListCache.UnloadObject(Obj: TObject; Index: Integer); var x: TLcObject; begin FObjectsStream.Seek(Index * SizeOf(x), soFromBeginning); FObjectsStream.Read(x, SizeOf(x)); { If an object has not been cached yet or size of objects can change dynamically, it must be cached now or again } if not FStaticSize or (x.Size = 0) then begin x.Offset := FCacheStream.Seek(0, soFromEnd); WriteObj(FCacheStream, Obj); x.Size := FCacheStream.Position - x.Offset; FObjectsStream.Seek(Index * SizeOf(x), soFromBeginning); FObjectsStream.Write(x, SizeOf(x)); end { If an object has been cached, it either can be cached again or just discarded } else if FUpdate then begin FCacheStream.Seek(x.Offset, soFromBeginning); WriteObj(FCacheStream, Obj); { This exception occurs when the writing routine attempts to write data over bounds that were assigned with the object before. The writing routine must write the same number of bytes for the same object every time. This exception may lead to corruption of consequent objects. } if x.Offset + x.Size <> FCacheStream.Position then raise Exception.Create('The written object corrupted the consequent object'); end; { Remove the object from memory } Obj.Free; end; procedure TListCache.PushObject(Obj: TObject; Index: Integer); var i: Integer; Tail: TLcLoadedObject; begin { If the queue is full, serialize tail objects and remove them from memory } Tail := FCacheQueue[Length(FCacheQueue) - 1]; if Tail.Reference <> nil then UnloadObject(Tail.Reference, Tail.Index); { Remove the tail from the queue and insert the new object index to the head of the queue } for i := Length(FCacheQueue) - 1 downto 1 do FCacheQueue[i] := FCacheQueue[i - 1]; FCacheQueue[0].Reference := Obj; FCacheQueue[0].Index := Index; end; { TArray } procedure TfrxArray.Append(const Value); begin Count := Count + 1; SetItem(Count - 1, Value) end; procedure TfrxArray.Clear; begin Count := 0 end; procedure TfrxArray.Delete(Index: Integer); var i: Integer; begin VerifyIndex(Index); for i := Index to Count - 2 do Move(ItemData[i + 1]^, ItemData[i]^, ItemSize); Count := Count - 1; end; destructor TfrxArray.Destroy; begin Clear; inherited; end; procedure TfrxArray.Exchange(Index1, Index2: Integer); var p0, p1, p2: Pointer; begin p1 := ItemData[Index1]; p2 := ItemData[Index2]; GetMem(p0, ItemSize); try Move(p1^, p0^, ItemSize); Move(p2^, p1^, ItemSize); Move(p0^, p2^, ItemSize); finally FreeMem(p0, ItemSize) end; end; procedure TfrxArray.Insert(Index: Integer; const Value); var i: Integer; begin VerifyIndex(Index); Count := Count + 1; for i := Count - 2 downto Index do Move(ItemData[i]^, ItemData[i + 1]^, ItemSize); Move(Value, ItemData[Index]^, ItemSize); end; procedure TfrxArray.GetItem(Index: Integer; var Item); begin VerifyIndex(Index); Move(GetItemData(Index)^, Item, ItemSize); end; procedure TfrxArray.SetItem(Index: Integer; const Item); begin VerifyIndex(Index); Move(Item, GetItemData(Index)^, ItemSize); end; procedure TfrxArray.Sort(Compare: TSimpleCompareFunction; min, max: Integer); var x: array of Byte; i, j: Integer; begin if min >= max then Exit; System.SetLength(x, ItemSize); GetItem((min + max) div 2, x[0]); i := min; j := max; while i <= j do begin while Compare(GetItemData(i), @x[0]) < 0 do Inc(i); while Compare(@x[0], GetItemData(j)) < 0 do Dec(j); if i <= j then begin Exchange(i, j); Inc(i); Dec(j); end; end; if min < j then Sort(Compare, min, j); if i < max then Sort(Compare, i, max); end; procedure TfrxArray.VerifyIndex(Index: Integer); begin Assert((Index >= 0) and (Index < Count), 'Index out of bounds') end; procedure TfrxArray.ResetItems(Min, Max: Integer); var i: Integer; begin if Zero then for i := Min to Max do ZeroMemory(ItemData[i], ItemSize) else for i := Min to Max do FillMemory(ItemData[i], ItemSize, $ff) end; { TCachedArray } constructor TfrxCachedArray.Create(DataStream, ServStream: TStream; Block, ItemSize: Integer); var p: TLcCreateParams; begin FBlock := Block; FItemSize := ItemSize; p.CacheStream := DataStream; p.ObjectsStream := ServStream; p.QueueSize := 8; FStorage := TListCache.Create(p); Initialize; end; constructor TfrxCachedArray.Create(DataFile, ServFile: string; Block, ItemSize: Integer); begin FBlock := Block; FItemSize := ItemSize; FStorage := TListCache.Create(DataFile, ServFile, 8); Initialize; end; destructor TfrxCachedArray.Destroy; begin FStorage.Free end; procedure TfrxCachedArray.Initialize; begin FStorage.ReadObj := DbRead; FStorage.WriteObj := DbWrite; FStorage.StaticSize := True; FZero := True; end; procedure TfrxCachedArray.Sort(Compare: TSimpleCompareFunction; Min, Max: Integer); procedure GetBlockBounds(Index: Integer; var min, max: Integer); begin min := Index * FBlock; max := min + FBlock - 1; if max >= Count then max := Count - 1; end; procedure SortBlock(Index: Integer); var min, max, i, j: Integer; x, y: Pointer; begin GetBlockBounds(Index, min, max); for i := min to max do for j := i + 1 to max do begin x := GetItemData(i); y := GetItemData(j); if Compare(x, y) >= 0 then Exchange(i, j); end; end; function BinSearch(const x: Pointer; min, max: Integer): Integer; var i: Integer; begin if min = max then begin Result := min; Exit; end; i := (min + max) div 2; if Compare(x, GetItemData(i)) < 0 then Result := BinSearch(x, min, i) else if i <> min then Result := BinSearch(x, i, max) else Result := i; end; procedure MergeBlock(Index: Integer); var i, j, min, max: Integer; x, y: array of Byte; begin GetBlockBounds(Index, min, max); System.SetLength(x, ItemSize); System.SetLength(y, ItemSize); GetItem(min, y[0]); for i := BinSearch(@y[0], 0, min - 1) to min - 1 do begin GetItem(i, x[0]); if Compare(@x[0], @y[0]) < 0 then Continue; SetItem(i, y[0]); j := min + 1; while j <= max do begin GetItem(j, y[0]); if Compare(@x[0], @y[0]) < 0 then Break; SetItem(j - 1, y[0]); Inc(j); end; SetItem(j - 1, x[0]); GetItem(min, y[0]); end; end; var i: Integer; begin if (Min <> 0) or (Max <> Count - 1) then begin inherited Sort(Compare, 0, Count - 1); Exit; end; if FStorage.Count < 1 then Exit; SortBlock(0); for i := 1 to FStorage.Count - 1 do begin SortBlock(i); MergeBlock(i); end; end; function TfrxCachedArray.GetCount: Integer; begin Result := FCount end; procedure TfrxCachedArray.SetCount(Value: Integer); var i, n: Integer; begin n := (Value + FBlock - 1) div FBlock; if Value > Count then for i := FStorage.Count to n - 1 do FStorage[i] := CreateBlock else FStorage.Count := n; FCount := Value; end; procedure TfrxCachedArray.SetZero(f: Boolean); begin FZero := f end; function TfrxCachedArray.GetItemData(Index: Integer): Pointer; var b: TDataBlock; begin b := FStorage[Index div FBlock] as TDataBlock; Result := Pointer(frxInteger(b.Data) + (Index mod FBlock) * ItemSize); end; function TfrxCachedArray.GetItemSize: Integer; begin Result := FItemSize end; function TfrxCachedArray.GetZero: Boolean; begin Result := FZero end; function TfrxCachedArray.CreateBlock: TDataBlock; begin Result := TDataBlock.Create(FBlock * ItemSize, Zero) end; { TDataBlock } constructor TDataBlock.Create(Size: Integer; Zero: Boolean); begin if Size = 0 then Exit; GetMem(Data, Size); if Zero then ZeroMemory(Data, Size) else {$IFNDEF FPC}FillMemory{$ELSE}FillByte{$ENDIF}(Data, Size, $ff); Self.Size := Size; end; destructor TDataBlock.Destroy; begin FreeMem(Data, Size) end; function DbRead(Stream: TStream): TObject; var n: Integer; b: TDataBlock; begin Stream.ReadBuffer(n, SizeOf(n)); b := TDataBlock.Create(n); Stream.ReadBuffer(b.Data^, n); Result := b; end; procedure DbWrite(Stream: TStream; Block: TObject); var b: TDataBlock; begin b := Block as TDataBlock; Stream.WriteBuffer(b.Size, SizeOf(b.Size)); if b.Size > 0 then Stream.WriteBuffer(b.Data^, b.Size) end; { TIntArrayBase } procedure TfrxIntArrayBase.Append(Value: Integer); begin if FCapacity < Count + 1 then Grow; Count := Count + 1; Items[Count - 1] := Value; end; function TfrxIntArrayBase.GetItemSize: Integer; begin Result := SizeOf(Items[0]) end; procedure TfrxIntArrayBase.Insert(Index, Value: Integer); begin if FCapacity < Count + 1 then Grow; inherited Insert(Index, Value) end; { TIntArray } function TfrxIntArray.GetItem(Index: Integer): Integer; begin Result := FArray[Index] end; procedure TfrxIntArray.SetItem(Index: Integer; Value: Integer); begin FArray[Index] := Value end; procedure TfrxIntArray.SetNewCapacity(Value: Integer); var n: Integer; begin n := Count; SetLength(FArray, Value); ResetItems(n, Value - 1); Inherited; end; procedure TfrxIntArray.SetZero(f: Boolean); begin FZero := f end; function TfrxIntArray.GetItemData(Index: Integer): Pointer; begin Result := @FArray[Index] end; function TfrxIntArray.GetZero: Boolean; begin Result := FZero end; { TCachedIntArray } constructor TfrxCachedIntArray.Create(DataStream, ServStream: TStream; Block: Integer); begin FArray := TfrxCachedArray.Create(DataStream, ServStream, Block, ItemSize) end; constructor TfrxCachedIntArray.Create(DataFile, ServFile: string; Block: Integer); begin FArray := TfrxCachedArray.Create(DataFile, ServFile, Block, ItemSize) end; destructor TfrxCachedIntArray.Destroy; begin FArray.Free; inherited; end; function TfrxCachedIntArray.GetCount: Integer; begin Result := FArray.Count end; function TfrxCachedIntArray.GetItem(Index: Integer): Integer; begin FArray.GetItem(Index, Result) end; function TfrxCachedIntArray.GetItemData(Index: Integer): Pointer; begin Result := FArray.ItemData[Index] end; function TfrxCachedIntArray.GetZero: Boolean; begin Result := FArray.Zero end; procedure TfrxCachedIntArray.SetCount(Value: Integer); begin FArray.Count := Value end; procedure TfrxCachedIntArray.SetItem(Index, Value: Integer); begin FArray.SetItem(Index, Value) end; procedure TfrxCachedIntArray.SetZero(f: Boolean); begin FArray.Zero := f end; { TExtArrayBase } procedure TfrxExtArrayBase.Append(Value: Extended); begin if FCapacity < Count + 1 then Grow; Count := Count + 1; Items[Count - 1] := Value; end; function TfrxExtArrayBase.GetItemSize: Integer; begin Result := SizeOf(Items[0]) end; procedure TfrxExtArrayBase.Insert(Index: Integer; Value: Extended); begin if FCapacity < Count + 1 then Grow; inherited Insert(Index, Value) end; { TExtArray } function TfrxExtArray.GetItem(Index: Integer): Extended; begin Result := FArray[Index] end; procedure TfrxExtArray.SetItem(Index: Integer; Value: Extended); begin FArray[Index] := Value end; procedure TfrxExtArray.SetNewCapacity(Value: Integer); var n: Integer; begin n := Count; SetLength(FArray, Value); ResetItems(n, Value - 1); Inherited; end; procedure TfrxExtArray.SetZero(f: Boolean); begin FZero := f end; function TfrxExtArray.GetItemData(Index: Integer): Pointer; begin Result := @FArray[Index] end; function TfrxExtArray.GetZero: Boolean; begin Result := FZero end; { TCachedExtArray } constructor TfrxCachedExtArray.Create(DataStream, ServStream: TStream; Block: Integer); begin FArray := TfrxCachedArray.Create(DataStream, ServStream, Block, ItemSize) end; constructor TfrxCachedExtArray.Create(DataFile, ServFile: string; Block: Integer); begin FArray := TfrxCachedArray.Create(DataFile, ServFile, Block, ItemSize) end; destructor TfrxCachedExtArray.Destroy; begin FArray.Free; inherited; end; function TfrxCachedExtArray.GetCount: Integer; begin Result := FArray.Count end; function TfrxCachedExtArray.GetItem(Index: Integer): Extended; begin FArray.GetItem(Index, Result) end; function TfrxCachedExtArray.GetItemData(Index: Integer): Pointer; begin Result := FArray.ItemData[Index] end; function TfrxCachedExtArray.GetZero: Boolean; begin Result := FArray.Zero end; procedure TfrxCachedExtArray.SetCount(Value: Integer); begin FArray.Count := Value end; procedure TfrxCachedExtArray.SetItem(Index: Integer; Value: Extended); begin FArray.SetItem(Index, Value) end; procedure TfrxCachedExtArray.SetZero(f: Boolean); begin FArray.Zero := f end; { TStreamRW } constructor TStreamRW.Create(Stream: TStream; Secure: Boolean); begin if Stream = nil then raise Exception.Create('Invalid stream'); FStream := Stream; FSecure := Secure; end; function TStreamRW.GetBoolType: Integer; begin Result := $31927381; end; function TStreamRW.GetExtType: Integer; begin Result := $11a328b2; end; function TStreamRW.GetIntType: Integer; begin Result := -$218a9320; end; function TStreamRW.GetStrType: Integer; begin Result := $71bcf389; end; function TStreamRW.ReadBool: Boolean; begin ReadType(GetBoolType); FStream.ReadBuffer(Result, SizeOf(Result)); end; function TStreamRW.ReadExt: Extended; begin ReadType(GetExtType); FStream.ReadBuffer(Result, SizeOf(Result)); end; function TStreamRW.ReadInt: Integer; begin ReadType(GetIntType); FStream.ReadBuffer(Result, SizeOf(Result)); end; function TStreamRW.ReadStr: WideString; var n: Integer; begin ReadType(GetStrType); n := 0; FStream.ReadBuffer(n, 2); if n = 0 then begin Result := ''; Exit; end; SetLength(Result, n); FStream.ReadBuffer(Result[1], n * SizeOf(Result[1])); end; procedure TStreamRW.ReadType(t: Integer); var x: Integer; begin if not FSecure then Exit; FStream.ReadBuffer(x, SizeOf(x)); if x <> t then raise Exception.Create('Incorrect data type'); end; procedure TStreamRW.WriteBool(x: Boolean); begin WriteType(GetBoolType); FStream.WriteBuffer(x, SizeOf(x)); end; procedure TStreamRW.WriteExt(x: Extended); begin WriteType(GetExtType); FStream.WriteBuffer(x, SizeOf(x)); end; procedure TStreamRW.WriteInt(x: Integer); begin WriteType(GetIntType); FStream.WriteBuffer(x, SizeOf(x)); end; procedure TStreamRW.WriteStr(x: WideString); var n: Integer; begin WriteType(GetStrType); n := Length(x); FStream.WriteBuffer(n, 2); if n > 0 then FStream.WriteBuffer(x[1], n * SizeOf(x[1])); end; procedure TStreamRW.WriteType(t: Integer); begin if FSecure then FStream.WriteBuffer(t, SizeOf(t)); end; { TBitArray } procedure TBitArray.BitAnd(Src: TBitArray); var i: Integer; begin Assert(Length <= Src.Length); for i := 0 to High(FData) do FData[i] := FData[i] and Src.FData[i]; Trunc; end; procedure TBitArray.BitOr(Src: TBitArray); var i: Integer; begin Assert(Length <= Src.Length); for i := 0 to High(FData) do FData[i] := FData[i] or Src.FData[i]; Trunc; end; procedure TBitArray.BitXor(Src: TBitArray); var i: Integer; begin Assert(Length <= Src.Length); for i := 0 to High(FData) do FData[i] := FData[i] xor Src.FData[i]; Trunc; end; function TBitArray.Clone: TBitArray; begin Result := TBitArray.Create; try Result.Length := Length; if Length > 0 then Move(FData[0], Result.FData[0], System.Length(FData)); Result.Trunc; except Result.Free; Result := nil; end; end; function TBitArray.GetNumOfSetBits: Integer; var i: Integer; begin Result := 0; for i := 0 to High(FData) do Inc(Result, NumSetBits[FData[i]]); end; function TBitArray.GetRightmostBitIndex: Integer; begin for Result := Length - 1 downto 0 do if Bits[Result] then Exit; Result := -1; end; procedure TBitArray.ResetBits; begin if System.Length(FData) > 0 then FillChar(FData[0], System.Length(FData), 0) end; procedure TBitArray.SaveToStream(Stream: TStream); begin if System.Length(FData) > 0 then Stream.WriteBuffer(FData[0], System.Length(FData)) end; procedure TBitArray.SetBit(Index: Integer; Value: Boolean); var i: Integer; begin Assert((Index >= 0) and (Index < Length)); i := Index shr 3; if Value then FData[i] := FData[i] or (1 shl (Index and 7)) else FData[i] := FData[i] and not (1 shl (Index and 7)) end; function TBitArray.GetBit(Index: Integer): Boolean; begin Assert((Index >= 0) and (Index < Length)); Result := (FData[Index shr 3] and (1 shl (Index and 7))) > 0 end; procedure TBitArray.SetBits; begin if System.Length(FData) > 0 then FillChar(FData[0], System.Length(FData), -1); Trunc; end; procedure TBitArray.SetBitsLength(NewLength: Integer); begin SetLength(FData, (NewLength + 7) shr 3); FLength := NewLength; Trunc; end; procedure TBitArray.Trunc; begin if (Length and 7) > 0 then FData[High(FData)] := FData[High(FData)] and ((1 shl (Length and 7)) - 1); end; { TCachedStream } constructor TCachedStream.Create(Stream: TStream; DeleteOnDestroy: Boolean); begin FStream := Stream; FDeleteOnDestroy := DeleteOnDestroy; CacheSize := 4096; // typical size of one cluster end; destructor TCachedStream.Destroy; begin FlushCache; if FDeleteOnDestroy then FStream.Free; inherited end; procedure TCachedStream.FlushCache; begin if FUsed <> FStream.Write(FChunk[0], FUsed) then begin {$IFDEF STORAGE_DEBUG} DbgPrint('TCachedStream.FlushCache failed'#10); FStream.Write(FChunk[0], FUsed); {$ENDIF} end; FUsed := 0; end; function TCachedStream.GetCacheSize: Integer; begin Result := Length(FChunk) end; function TCachedStream.Read(var Buffer; Count: Integer): Longint; begin raise Exception.CreateFmt('%s does not support reading', [ClassName]) end; function TCachedStream.Seek(Offset: Integer; Origin: Word): Longint; begin FlushCache; Result := FStream.Seek(Offset, Origin); end; procedure TCachedStream.SetCacheSize(Size: Integer); begin if FUsed > 0 then raise Exception.Create('Changing cache size is allowed only when the cache is empty'); SetLength(FChunk, Size) end; function TCachedStream.Write(const Buffer; Count: Integer): Longint; var n, p: Integer; begin Result := Count; p := 0; while Count > 0 do begin n := Min(Count, Length(FChunk) - FUsed); Move(Pointer(frxInteger(@Buffer) + p)^, FChunk[FUsed], n); Inc(p, n); Inc(FUsed, n); Dec(Count, n); if FUsed = Length(FChunk) then FlushCache end; end; { TBase64Encoder } constructor TBase64Encoder.Create(Output: TStream); begin FOutput := Output; InitMap; end; destructor TBase64Encoder.Destroy; begin Finalise; inherited; end; procedure TBase64Encoder.Encode(a, n: Integer); var b: Integer; begin b := FMap[a shr 2 and 63] or FMap[a shr 12 and 15 or a and 3 shl 4 and 63] shl 8 or FMap[a shr 22 and 3 or a shr 8 and 15 shl 2 and 63] shl 16 or FMap[a shr 16 and 63] shl 24; case n of 1: FOutput.Write(b, 2); 2: FOutput.Write(b, 3); 3: FOutput.Write(b, 4); end; end; procedure TBase64Encoder.Finalise; var b: Byte; begin Encode(FCache, FUsed); b := Byte('='); while (FUsed > 0) and (FUsed < 3) do begin FOutput.Write(b, 1); Inc(FUsed); end; end; procedure TBase64Encoder.InitMap; procedure Map(Dest, Src, Count: Integer); var i: Integer; begin for i := 0 to Count - 1 do FMap[Dest + i] := Src + i end; begin Map(0, Byte('A'), 26); // A..Z Map(26, Byte('a'), 26); // a..z Map(52, Byte('0'), 10); // 0..9 FMap[62] := Byte('+'); // + FMap[63] := Byte('/'); // / end; function TBase64Encoder.Read(var Buffer; Count: Integer): Longint; begin raise Exception.Create('This stream is write-only') end; function TBase64Encoder.This: TBase64Encoder; begin Result := Self end; function TBase64Encoder.Write(const Buffer; Count: Integer): Longint; function BitMask(n: Integer): Integer; begin Result := (1 shl (8*n)) - 1 end; var n: Integer; begin if Count = 0 then {do nothing} else if FUsed > 0 then begin n := Min(3 - FUsed, Count); FCache := FCache or ((PInteger(@Buffer)^ and BitMask(n)) shl (8*FUsed)); Inc(FUsed, n); if FUsed = 3 then begin Encode(FCache, 3); FUsed := 0; end; Write(Pointer(frxInteger(@Buffer) + n)^, Count - n); end else begin n := 0; while n + 3 <= Count do begin Encode(PInteger(frxInteger(@Buffer) + n)^, 3); Inc(n, 3); end; if n < Count then begin FCache := PInteger(frxInteger(@Buffer) + n)^ and BitMask(Count - n); FUsed := Count - n; end; end; Result := Count end; { TFmtStream } constructor TFmtStream.Create(Output: TStream; Own: Boolean); begin FOutput := Output; FOwn := Own; end; destructor TFmtStream.Destroy; begin if FOwn then FOutput.Free; inherited; end; procedure TFmtStream.IncIndent(Step: Integer); begin Inc(FIndent, Step) end; procedure TFmtStream.PutsA(const Fmt: AnsiString; const Args: array of const); begin Puts(string(Fmt), Args) end; procedure TFmtStream.PutsA(const s: AnsiString); var i: Integer; begin if Formatted then for i := 1 to Indent do PutsRawA(' '); PutsRawA(s); if Formatted then PutsRawA(AnsiString(#13#10)) end; procedure TFmtStream.Puts(const Fmt: string; const Args: array of const); begin Puts(Format(Fmt, Args)) end; procedure TFmtStream.PutsRaw(const s: string); begin PutsRawA(AnsiString(s)) end; procedure TFmtStream.PutsRawA(const s: AnsiString); begin if s <> '' then Write(s[1], Length(s)) end; procedure TFmtStream.PutsRaw(const Fmt: string; const Args: array of const); begin PutsRaw(Format(Fmt, Args)) end; procedure TFmtStream.PutsRawA(const Fmt: AnsiString; const Args: array of const); begin PutsRaw(string(Fmt), Args) end; procedure TFmtStream.Puts(const s: string); begin PutsA(AnsiString(s)) end; function TFmtStream.Read(var Buffer; Count: Integer): Longint; begin Result := FOutput.Read(Buffer, Count) end; function TFmtStream.Write(const Buffer; Count: Integer): Longint; begin Result := FOutput.Write(Buffer, Count) end; { TProxyStream } constructor TProxyStream.Create(Stream: TStream; Offset, Size: Integer); begin Init(Stream, Offset, Size) end; procedure TProxyStream.Init(Stream: TStream; Offset, Size: Integer); begin if Stream is TProxyStream then Init(TProxyStream(Stream).FStream, Offset + TProxyStream(Stream).FOffset, Size) else begin FStream := Stream; FOffset := Offset; FSize := Size end; end; function TProxyStream.AdjustRange(var Len: Integer): Boolean; begin Result := (FPos >= 0) and (FPos < FSize); if FPos + Len > FSize then Len := FSize - FPos; end; function TProxyStream.Read(var Buffer; Count: Integer): Longint; var p: Longint; begin Result := 0; if not AdjustRange(Count) then Exit; p := FStream.Position; FStream.Position := FOffset + FPos; Result := FStream.Read(Buffer, Count); FStream.Position := p; FPos := FPos + Result; end; function TProxyStream.Write(const Buffer; Count: Integer): Longint; var p: Longint; begin Result := 0; if not AdjustRange(Count) then Exit; p := FStream.Position; FStream.Position := FOffset + FPos; Result := FStream.Write(Buffer, Count); FStream.Position := p; FPos := FPos + Result; end; function TProxyStream.Seek(Offset: Integer; Origin: Word): Longint; begin case Origin of soFromBeginning: FPos := Offset; soFromCurrent: FPos := FPos + Offset; soFromEnd: FPos := FSize + Offset; end; Result := FPos; end; { THexEncoder } constructor THexEncoder.Create(Output: TStream); begin FOutput := Output end; function THexEncoder.Read(var Buffer; Count: Integer): Longint; begin raise ENotImplemented.Create('This stream is read-only') end; function THexEncoder.Write(const Buffer; Count: Integer): Longint; const h: AnsiString = '0123456789abcdef'; var i: Integer; b: Byte; s: array[0..1] of AnsiChar; begin for i := 0 to Count - 1 do begin b := PByte(frxInteger(@Buffer) + i)^; s[0] := h[1 + (b shr 4)]; s[1] := h[1 + (b and $f)]; FOutput.WriteBuffer(s[0], 2); end; Result := Count; end; { TLineSplitter } constructor TLineSplitter.Create(Output: TStream; Length: Integer; Sep: AnsiString); begin Assert(Sep <> '', 'This class is useless for empty separators'); inherited Create; FOutput := Output; FLength := Length; FSep := Sep; end; function TLineSplitter.Read(var Buffer; Count: Integer): Longint; begin raise ENotSupportedException.Create('The stream is read-only') end; function TLineSplitter.Write(const Buffer; Count: Integer): Longint; var n: Integer; p: Pointer; begin Result := Count; p := @Buffer; while Count > 0 do begin n := Min(FLength - FWritten, Count); FOutput.WriteBuffer(p^, n); p := Pointer(frxInteger(p) + n); Inc(FWritten, n); Dec(Count, n); if FWritten = FLength then begin FOutput.WriteBuffer(FSep[1], Length(FSep)); FWritten := 0; end; end; end; { TBTreeNode } constructor TBTreeNode.Create(MaxLen: Integer); begin Assert(MaxLen > 0); SetLength(FKeys, MaxLen); SetLength(FVals, MaxLen); SetLength(FRefs, MaxLen + 1); end; function TBTreeNode.GetKey(i: Integer): Pointer; begin Assert((i >= 0) and (i < Len)); Result := FKeys[i] end; function TBTreeNode.GetRef(i: Integer): TBTreeNode; begin Assert((i >= 0) and (i < Len + 1)); Result := FRefs[i] end; function TBTreeNode.GetVal(i: Integer): Pointer; begin Assert((i >= 0) and (i < Len)); Result := FVals[i] end; procedure TBTreeNode.SetKey(i: Integer; k: Pointer); begin Assert((i >= 0) and (i < Len)); FKeys[i] := k end; procedure TBTreeNode.SetLen(n: Integer); begin Assert((n >= 0) and (n <= Length(FKeys))); FLen := n; end; procedure TBTreeNode.SetRef(i: Integer; r: TBTreeNode); begin Assert((i >= 0) and (i < Len +1)); FRefs[i] := r end; procedure TBTreeNode.SetVal(i: Integer; v: Pointer); begin Assert((i >= 0) and (i < Len)); FVals[i] := v end; { TBTree } constructor TBTree.Create(Order: Integer); begin Assert(Order >= 4); Assert(Order mod 2 = 0); FOrder := Order; end; destructor TBTree.Destroy; procedure DeleteNode(Node: TBTreeNode); var i: Integer; begin if Node = nil then Exit; for i := 0 to Node.Len do DeleteNode(Node.Refs[i]); Node.Free; end; begin DeleteNode(FRoot); inherited; end; function TBTree.FindIndex(Node: TBTreeNode; Key: Pointer): Integer; function LinSearch(Node: TBTreeNode; Key: Pointer): Integer; var i: Integer; begin i := 0; with Node do while (i < Len) and (frxCardinal(Keys[i]) < frxCardinal(Key)) do Inc(i); Result := i; end; function BinSearch(Node: TBTreeNode; Key: Pointer): Integer; var i, j, m: Integer; k: Pointer; begin if Node.Len = 0 then Result := 0 else with Node do begin i := 0; j := Len - 1; repeat m := (i + j) div 2; // due to i and j are small, the expression i + (j - i) div 2 can be avoided k := Keys[m]; if frxCardinal(Key) < frxCardinal(k) then j := m - 1 else if frxCardinal(Key) > frxCardinal(k) then i := m + 1 else Break; until i > j; if frxCardinal(k) < frxCardinal(Key) then Result := m + 1 else Result := m end; end; begin Assert(BinSearch(Node, Key) = LinSearch(Node, Key)); if Node.Len < 10 then Result := LinSearch(Node, Key) else Result := BinSearch(Node, Key) end; function TBTree.FindKey(Key: Pointer; out KeyIndex: Integer): TBTreeNode; var Node: TBTreeNode; i: Integer; begin if FRoot = nil then begin Result := nil; Exit; end; Node := FRoot; repeat Assert(Node <> nil); with Node do begin i := FindIndex(Node, Key); if (i < Len) and (Key = Keys[i]) then begin KeyIndex := i; Result := Node; Exit; end; Node := Refs[i]; end; until Node = nil; Result := nil; end; function TBTree.CreateNode(LNode: TBTreeNode): TBTreeNode; begin Result := TBTreeNode.Create(FOrder - 1); Result.Refs[0] := LNode; end; function TBTree.IsFull(Node: TBTreeNode): Boolean; begin Result := Node.Len + 1 = FOrder end; function TBTree.GetHeight: Integer; var Node: TBTreeNode; begin Result := 0; Node := FRoot; while Node <> nil do begin Inc(Result); Node := Node.Refs[0]; end; end; function TBTree.GetItem(Key: Pointer): Pointer; var Node: TBTreeNode; i: Integer; begin Node := FindKey(Key, i); if Node = nil then Result := nil else Result := Node.Vals[i] end; procedure TBTree.GetKeysAndValues(Keys, Values: TList); procedure Visit(Node: TBTreeNode); var i: Integer; begin if Node = nil then Exit; Visit(Node.Refs[0]); for i := 0 to Node.Len - 1 do begin if Keys <> nil then Keys.Add(Node.Keys[i]); if Values <> nil then Values.Add(Node.Vals[i]); Visit(Node.Refs[i + 1]); end; end; var n: Integer; begin n := KeysCount; if Keys <> nil then Keys.Capacity := n; if Values <> nil then Values.Capacity := n; if (Keys <> nil) or (Values <> nil) then Visit(FRoot); end; function TBTree.GetKeysCount: Integer; function Count(Node: TBTreeNode): Integer; var i: Integer; begin if Node = nil then Result := 0 else begin Result := Node.Len; for i := 0 to Node.Len do Inc(Result, Count(Node.Refs[i])); end; end; begin Result := Count(FRoot) end; procedure TBTree.SetItem(Key, Value: Pointer); begin Insert(Key, Value); end; procedure TBTree.SetItemExistence(Key: Pointer; b: Boolean); begin if b then Insert(Key, nil) else raise ENotImplemented.Create('Removing keys not implemented') end; function TBTree.ItemExists(Key: Pointer): Boolean; var i: Integer; begin Result := FindKey(Key, i) <> nil end; procedure TBTree.InsertKey(Node: TBTreeNode; i: Integer; Key, Value: Pointer; LNode, RNode: TBTreeNode); var j: Integer; begin Assert(not IsFull(Node)); Assert((i >= 0) and (i <= Node.Len)); with Node do begin Len := Len + 1; for j := Len - 1 downto i + 1 do Keys[j] := Keys[j - 1]; for j := Len - 1 downto i + 1 do Vals[j] := Vals[j - 1]; for j := Len downto i + 2 do Refs[j] := Refs[j - 1]; Keys[i] := Key; Vals[i] := Value; Refs[i] := LNode; Refs[i + 1] := RNode; end; end; procedure TBTree.SplitTree(Node: TBTreeNode; i: Integer); var LNode, RNode: TBTreeNode; Key, Value: Pointer; j, m: Integer; begin Assert(not IsFull(Node)); Assert(Node.Refs[i] <> nil); Assert(IsFull(Node.Refs[i])); LNode := Node.Refs[i]; RNode := CreateNode; with LNode do begin m := Len div 2; Key := Keys[m]; Value := Vals[m]; RNode.Len := m; for j := 0 to m - 1 do RNode.Keys[j] := Keys[m + 1 + j]; for j := 0 to m - 1 do RNode.Vals[j] := Vals[m + 1 + j]; for j := 0 to m do RNode.Refs[j] := Refs[m + 1 + j]; Len := m; end; with Node do begin Assert(Refs[i] = LNode); InsertKey(Node, i, Key, Value, LNode, RNode); end; end; function TBTree.Insert(Key: Pointer; Value: Pointer): Boolean; var Node: TBTreeNode; i: Integer; begin if FRoot = nil then begin FRoot := CreateNode; FRoot.Len := 1; FRoot.Keys[0] := Key; FRoot.Vals[0] := Value; Result := True; Exit; end; if IsFull(FRoot) then begin FRoot := CreateNode(FRoot); SplitTree(FRoot, 0); end; Result := False; Node := FRoot; repeat Assert(Node <> nil); with Node do begin i := FindIndex(Node, Key); if (i < Len) and (Key = Keys[i]) then Break; if (Refs[i] = nil) or not IsFull(Refs[i]) then begin if Refs[i] = nil then begin InsertKey(Node, i, Key, Value, nil, nil); Result := True; end; Node := Refs[i] end else begin SplitTree(Node, i); if Key = Keys[i] then Break; if frxCardinal(Key) < frxCardinal(Keys[i]) then Node := Refs[i] else Node := Refs[i + 1]; end; end; until Node = nil; if Node <> nil then Node.Vals[i] := Value; end; { TAVLTree } procedure TAVLTree.LiftChild(R, N: TAVLTreeNode; Leaf: Boolean); var A, B: TAVLTreeNode; begin Assert(N <> nil); Assert(N[Leaf] <> nil); A := N[Leaf]; B := A[not Leaf]; A[not Leaf] := N; N[Leaf] := B; if R = nil then FRoot := A else R[R[True] = N] := A; N.UpdateHeight; A.UpdateHeight; end; procedure TAVLTree.Insert(Parent, Node: TAVLTreeNode; Key, Value: Pointer); var Leaf: Boolean; begin Assert(Node <> nil); if Node.Key = Key then begin Node.Value := Value; Exit; end; Leaf := frxCardinal(Key) < frxCardinal(Node.Key); if Node[Leaf] = nil then begin Node[Leaf] := TAVLTreeNode.Create(Key, Value); Node.Height := 2; Exit; end; Insert(Node, Node[Leaf], Key, Value); Node.UpdateHeight; if Node[Leaf].Height < 2 + Node[not Leaf].Height then Exit; if Node[Leaf][not Leaf].Height > Node[Leaf][Leaf].Height then LiftChild(Node, Node[Leaf], not Leaf); LiftChild(Parent, Node, Leaf); end; function TAVLTree.GetItem(Key: Pointer): Pointer; var Node: TAVLTreeNode; begin if FindKey(Key, Node) then Result := Node.Value else Result := nil end; procedure TAVLTree.SetItem(Key, Value: Pointer); begin if FRoot = nil then FRoot := TAVLTreeNode.Create(Key, Value) else Insert(nil, FRoot, Key, Value) end; function TAVLTree.ItemExists(Key: Pointer): Boolean; var Node: TAVLTreeNode; begin Result := FindKey(Key, Node) end; procedure TAVLTree.SetItemExistence(Key: Pointer; b: Boolean); begin if b then SetItem(Key, nil) else raise ENotImplemented.Create('Item cannot be removed') end; destructor TAVLTree.Destroy; begin Root.Free; inherited; end; function TAVLTree.FindKey(Key: Pointer; out Node: TAVLTreeNode): Boolean; var N: TAVLTreeNode; begin N := FRoot; while (N <> nil) and (N.Key <> Key) do N := N[frxCardinal(Key) < frxCardinal(N.Key)]; Node := N; Result := N <> nil; end; { TAVLTreeNode } constructor TAVLTreeNode.Create(Key, Value: Pointer); begin FKey := Key; FValue := Value; FHeight := 1; end; destructor TAVLTreeNode.Destroy; begin FLeafs[True].Free; FLeafs[False].Free; inherited; end; function TAVLTreeNode.GetChild(Leaf: Boolean): TAVLTreeNode; begin Result := FLeafs[Leaf] end; function TAVLTreeNode.GetHeight: Byte; begin if Self = nil then Result := 0 else Result := FHeight end; procedure TAVLTreeNode.SetChild(Leaf: Boolean; Node: TAVLTreeNode); begin FLeafs[Leaf] := Node; end; procedure TAVLTreeNode.UpdateHeight; begin FHeight := Max(Child[True].Height, Child[False].Height) + 1 end; { TfrxBaseArray } procedure TfrxBaseArray.Clear; begin inherited; SetNewCapacity(0); end; function TfrxBaseArray.GetCount: Integer; begin Result := FCount; end; procedure TfrxBaseArray.Grow; var Delta: Integer; begin if FCapacity > 64 then Delta := FCapacity div 4 else if FCapacity > 8 then Delta := 16 else Delta := 4; SetNewCapacity(FCapacity + Delta); end; procedure TfrxBaseArray.SetCount(Value: Integer); begin // if Value < 0 then // Error(@SListCountError, Value); if Value <> Count then begin if Value > FCapacity then SetNewCapacity(Value); // if NewCount > FCount then // FillChar(FList[FCount], (NewCount - FCount) * SizeOf(Pointer), 0) end; FCount := Value; end; procedure TfrxBaseArray.SetNewCapacity(Value: Integer); begin FCapacity := Value; end; { TfrxExtendexObjectList } procedure TfrxExtendedObjectList.AppendObject(Value: Extended; aObject: TObject); begin Append(Value); FObjectList[Count - 1] := aObject; end; procedure TfrxExtendedObjectList.Clear; var i: Integer; begin if FFreeObjects then for i := 0 to Length(FObjectList) - 1 do if Assigned(FObjectList[i]) then FObjectList[i].Free; inherited; end; procedure TfrxExtendedObjectList.Delete(Index: Integer); var i: Integer; begin for i := Index to Count - 2 do FObjectList[i] := FObjectList[i + 1]; inherited Delete(Index); end; procedure TfrxExtendedObjectList.Exchange(Index1, Index2: Integer); var tmpObj: TObject; begin inherited Exchange(Index1, Index2); tmpObj := FObjectList[Index1]; FObjectList[Index1] := FObjectList[Index2]; FObjectList[Index2] := tmpObj; end; function TfrxExtendedObjectList.GetObject(Index: Integer): TObject; begin Result := FObjectList[Index]; end; procedure TfrxExtendedObjectList.InsertObject(Index: Integer; Value: Extended; aObject: TObject); var i: Integer; begin if Count = 0 then begin AppendObject(Value, aObject); Exit; end; Insert(Index, Value); for i := Count - 2 downto Index do FObjectList[i + 1] := FObjectList[i]; FObjectList[Index] := aObject; end; procedure TfrxExtendedObjectList.SetNewCapacity(Value: Integer); begin inherited; SetLength(FObjectList, Value); end; procedure TfrxExtendedObjectList.SetObject(Index: Integer; const Value: TObject); begin FObjectList[Index] := Value; end; function DoCompare(const x, y: Pointer): Integer; var pX, pY: ^Extended; begin pX := x; pY := y; Result := 0; if Round(pX^ * 100) > Round(pY^ *100) then Result := 1 else if Round(pX^ * 100) < Round(pY^ * 100) then Result := -1 end; procedure TfrxExtendedObjectList.SortList; begin Sort(@DoCompare, 0, Count - 1); end; initialization InitNumSetBits; end.